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, 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 false,
711 Highlights::default(),
712 )
713 .map(|c| c.text)
714 .collect()
715 }
716
717 #[cfg(test)]
718 pub fn fold_count(&self) -> usize {
719 self.folds.items(&self.inlay_snapshot.buffer).len()
720 }
721
722 #[ztracing::instrument(skip_all)]
723 pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> MBTextSummary {
724 let mut summary = MBTextSummary::default();
725
726 let mut cursor = self
727 .transforms
728 .cursor::<Dimensions<FoldPoint, InlayPoint>>(());
729 cursor.seek(&range.start, Bias::Right);
730 if let Some(transform) = cursor.item() {
731 let start_in_transform = range.start.0 - cursor.start().0.0;
732 let end_in_transform = cmp::min(range.end, cursor.end().0).0 - cursor.start().0.0;
733 if let Some(placeholder) = transform.placeholder.as_ref() {
734 summary = MBTextSummary::from(
735 &placeholder.text.as_ref()
736 [start_in_transform.column as usize..end_in_transform.column as usize],
737 );
738 } else {
739 let inlay_start = self
740 .inlay_snapshot
741 .to_offset(InlayPoint(cursor.start().1.0 + start_in_transform));
742 let inlay_end = self
743 .inlay_snapshot
744 .to_offset(InlayPoint(cursor.start().1.0 + end_in_transform));
745 summary = self
746 .inlay_snapshot
747 .text_summary_for_range(inlay_start..inlay_end);
748 }
749 }
750
751 if range.end > cursor.end().0 {
752 cursor.next();
753 summary += cursor
754 .summary::<_, TransformSummary>(&range.end, Bias::Right)
755 .output;
756 if let Some(transform) = cursor.item() {
757 let end_in_transform = range.end.0 - cursor.start().0.0;
758 if let Some(placeholder) = transform.placeholder.as_ref() {
759 summary += MBTextSummary::from(
760 &placeholder.text.as_ref()[..end_in_transform.column as usize],
761 );
762 } else {
763 let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
764 let inlay_end = self
765 .inlay_snapshot
766 .to_offset(InlayPoint(cursor.start().1.0 + end_in_transform));
767 summary += self
768 .inlay_snapshot
769 .text_summary_for_range(inlay_start..inlay_end);
770 }
771 }
772 }
773
774 summary
775 }
776
777 #[ztracing::instrument(skip_all)]
778 pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
779 let (start, end, item) = self
780 .transforms
781 .find::<Dimensions<InlayPoint, FoldPoint>, _>((), &point, Bias::Right);
782 if item.is_some_and(|t| t.is_fold()) {
783 if bias == Bias::Left || point == start.0 {
784 start.1
785 } else {
786 end.1
787 }
788 } else {
789 let overshoot = point.0 - start.0.0;
790 FoldPoint(cmp::min(start.1.0 + overshoot, end.1.0))
791 }
792 }
793
794 #[ztracing::instrument(skip_all)]
795 pub fn fold_point_cursor(&self) -> FoldPointCursor<'_> {
796 let cursor = self
797 .transforms
798 .cursor::<Dimensions<InlayPoint, FoldPoint>>(());
799 FoldPointCursor { cursor }
800 }
801
802 #[ztracing::instrument(skip_all)]
803 pub fn len(&self) -> FoldOffset {
804 FoldOffset(self.transforms.summary().output.len)
805 }
806
807 #[ztracing::instrument(skip_all)]
808 pub fn line_len(&self, row: u32) -> u32 {
809 let line_start = FoldPoint::new(row, 0).to_offset(self).0;
810 let line_end = if row >= self.max_point().row() {
811 self.len().0
812 } else {
813 FoldPoint::new(row + 1, 0).to_offset(self).0 - 1
814 };
815 (line_end - line_start) as u32
816 }
817
818 #[ztracing::instrument(skip_all)]
819 pub fn row_infos(&self, start_row: u32) -> FoldRows<'_> {
820 if start_row > self.transforms.summary().output.lines.row {
821 panic!("invalid display row {}", start_row);
822 }
823
824 let fold_point = FoldPoint::new(start_row, 0);
825 let mut cursor = self
826 .transforms
827 .cursor::<Dimensions<FoldPoint, InlayPoint>>(());
828 cursor.seek(&fold_point, Bias::Left);
829
830 let overshoot = fold_point.0 - cursor.start().0.0;
831 let inlay_point = InlayPoint(cursor.start().1.0 + overshoot);
832 let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
833
834 FoldRows {
835 fold_point,
836 input_rows,
837 cursor,
838 }
839 }
840
841 #[ztracing::instrument(skip_all)]
842 pub fn max_point(&self) -> FoldPoint {
843 FoldPoint(self.transforms.summary().output.lines)
844 }
845
846 #[cfg(test)]
847 pub fn longest_row(&self) -> u32 {
848 self.transforms.summary().output.longest_row
849 }
850
851 #[ztracing::instrument(skip_all)]
852 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
853 where
854 T: ToOffset,
855 {
856 let buffer = &self.inlay_snapshot.buffer;
857 let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
858 let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
859 iter::from_fn(move || {
860 let item = folds.item();
861 folds.next();
862 item
863 })
864 }
865
866 #[ztracing::instrument(skip_all)]
867 pub fn intersects_fold<T>(&self, offset: T) -> bool
868 where
869 T: ToOffset,
870 {
871 let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
872 let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
873 let (_, _, item) = self
874 .transforms
875 .find::<InlayOffset, _>((), &inlay_offset, Bias::Right);
876 item.is_some_and(|t| t.placeholder.is_some())
877 }
878
879 #[ztracing::instrument(skip_all)]
880 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
881 let mut inlay_point = self
882 .inlay_snapshot
883 .to_inlay_point(Point::new(buffer_row.0, 0));
884 let mut cursor = self.transforms.cursor::<InlayPoint>(());
885 cursor.seek(&inlay_point, Bias::Right);
886 loop {
887 match cursor.item() {
888 Some(transform) => {
889 let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
890 if buffer_point.row != buffer_row.0 {
891 return false;
892 } else if transform.placeholder.is_some() {
893 return true;
894 }
895 }
896 None => return false,
897 }
898
899 if cursor.end().row() == inlay_point.row() {
900 cursor.next();
901 } else {
902 inlay_point.0 += Point::new(1, 0);
903 cursor.seek(&inlay_point, Bias::Right);
904 }
905 }
906 }
907
908 #[ztracing::instrument(skip_all)]
909 pub(crate) fn chunks<'a>(
910 &'a self,
911 range: Range<FoldOffset>,
912 language_aware: bool,
913 highlights: Highlights<'a>,
914 ) -> FoldChunks<'a> {
915 let mut transform_cursor = self
916 .transforms
917 .cursor::<Dimensions<FoldOffset, InlayOffset>>(());
918 transform_cursor.seek(&range.start, Bias::Right);
919
920 let inlay_start = {
921 let overshoot = range.start - transform_cursor.start().0;
922 transform_cursor.start().1 + overshoot
923 };
924
925 let transform_end = transform_cursor.end();
926
927 let inlay_end = if transform_cursor
928 .item()
929 .is_none_or(|transform| transform.is_fold())
930 {
931 inlay_start
932 } else if range.end < transform_end.0 {
933 let overshoot = range.end - transform_cursor.start().0;
934 transform_cursor.start().1 + overshoot
935 } else {
936 transform_end.1
937 };
938
939 FoldChunks {
940 transform_cursor,
941 inlay_chunks: self.inlay_snapshot.chunks(
942 inlay_start..inlay_end,
943 language_aware,
944 highlights,
945 ),
946 inlay_chunk: None,
947 inlay_offset: inlay_start,
948 output_offset: range.start,
949 max_output_offset: range.end,
950 }
951 }
952
953 #[ztracing::instrument(skip_all)]
954 pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
955 self.chunks(
956 start.to_offset(self)..self.len(),
957 false,
958 Highlights::default(),
959 )
960 .flat_map(|chunk| chunk.text.chars())
961 }
962
963 #[ztracing::instrument(skip_all)]
964 pub fn chunks_at(&self, start: FoldPoint) -> FoldChunks<'_> {
965 self.chunks(
966 start.to_offset(self)..self.len(),
967 false,
968 Highlights::default(),
969 )
970 }
971
972 #[cfg(test)]
973 #[ztracing::instrument(skip_all)]
974 pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
975 if offset > self.len() {
976 self.len()
977 } else {
978 self.clip_point(offset.to_point(self), bias).to_offset(self)
979 }
980 }
981
982 #[ztracing::instrument(skip_all)]
983 pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
984 let (start, end, item) = self
985 .transforms
986 .find::<Dimensions<FoldPoint, InlayPoint>, _>((), &point, Bias::Right);
987 if let Some(transform) = item {
988 let transform_start = start.0.0;
989 if transform.placeholder.is_some() {
990 if point.0 == transform_start || matches!(bias, Bias::Left) {
991 FoldPoint(transform_start)
992 } else {
993 FoldPoint(end.0.0)
994 }
995 } else {
996 let overshoot = InlayPoint(point.0 - transform_start);
997 let inlay_point = start.1 + overshoot;
998 let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
999 FoldPoint(start.0.0 + (clipped_inlay_point - start.1).0)
1000 }
1001 } else {
1002 FoldPoint(self.transforms.summary().output.lines)
1003 }
1004 }
1005}
1006
1007pub struct FoldPointCursor<'transforms> {
1008 cursor: Cursor<'transforms, 'static, Transform, Dimensions<InlayPoint, FoldPoint>>,
1009}
1010
1011impl FoldPointCursor<'_> {
1012 #[ztracing::instrument(skip_all)]
1013 pub fn map(&mut self, point: InlayPoint, bias: Bias) -> FoldPoint {
1014 let cursor = &mut self.cursor;
1015 if cursor.did_seek() {
1016 cursor.seek_forward(&point, Bias::Right);
1017 } else {
1018 cursor.seek(&point, Bias::Right);
1019 }
1020 if cursor.item().is_some_and(|t| t.is_fold()) {
1021 if bias == Bias::Left || point == cursor.start().0 {
1022 cursor.start().1
1023 } else {
1024 cursor.end().1
1025 }
1026 } else {
1027 let overshoot = point.0 - cursor.start().0.0;
1028 FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0))
1029 }
1030 }
1031}
1032
1033fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: MBTextSummary) {
1034 let mut did_merge = false;
1035 transforms.update_last(
1036 |last| {
1037 if !last.is_fold() {
1038 last.summary.input += summary;
1039 last.summary.output += summary;
1040 did_merge = true;
1041 }
1042 },
1043 (),
1044 );
1045 if !did_merge {
1046 transforms.push(
1047 Transform {
1048 summary: TransformSummary {
1049 input: summary,
1050 output: summary,
1051 },
1052 placeholder: None,
1053 },
1054 (),
1055 )
1056 }
1057}
1058
1059fn intersecting_folds<'a>(
1060 inlay_snapshot: &'a InlaySnapshot,
1061 folds: &'a SumTree<Fold>,
1062 range: Range<MultiBufferOffset>,
1063 inclusive: bool,
1064) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, MultiBufferOffset> {
1065 let buffer = &inlay_snapshot.buffer;
1066 let start = buffer.anchor_before(range.start.to_offset(buffer));
1067 let end = buffer.anchor_after(range.end.to_offset(buffer));
1068 let mut cursor = folds.filter::<_, MultiBufferOffset>(buffer, move |summary| {
1069 let start_cmp = start.cmp(&summary.max_end, buffer);
1070 let end_cmp = end.cmp(&summary.min_start, buffer);
1071
1072 if inclusive {
1073 start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
1074 } else {
1075 start_cmp == Ordering::Less && end_cmp == Ordering::Greater
1076 }
1077 });
1078 cursor.next();
1079 cursor
1080}
1081
1082fn consolidate_inlay_edits(mut edits: Vec<InlayEdit>) -> Vec<InlayEdit> {
1083 edits.sort_unstable_by(|a, b| {
1084 a.old
1085 .start
1086 .cmp(&b.old.start)
1087 .then_with(|| b.old.end.cmp(&a.old.end))
1088 });
1089
1090 let _old_alloc_ptr = edits.as_ptr();
1091 let mut inlay_edits = edits.into_iter();
1092
1093 if let Some(mut first_edit) = inlay_edits.next() {
1094 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
1095 #[allow(clippy::filter_map_identity)]
1096 let mut v: Vec<_> = inlay_edits
1097 .scan(&mut first_edit, |prev_edit, edit| {
1098 if prev_edit.old.end >= edit.old.start {
1099 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
1100 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
1101 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
1102 Some(None) // Skip this edit, it's merged
1103 } else {
1104 let prev = std::mem::replace(*prev_edit, edit);
1105 Some(Some(prev)) // Yield the previous edit
1106 }
1107 })
1108 .filter_map(|x| x)
1109 .collect();
1110 v.push(first_edit.clone());
1111 debug_assert_eq!(_old_alloc_ptr, v.as_ptr(), "Inlay edits were reallocated");
1112 v
1113 } else {
1114 vec![]
1115 }
1116}
1117
1118fn consolidate_fold_edits(mut edits: Vec<FoldEdit>) -> Vec<FoldEdit> {
1119 edits.sort_unstable_by(|a, b| {
1120 a.old
1121 .start
1122 .cmp(&b.old.start)
1123 .then_with(|| b.old.end.cmp(&a.old.end))
1124 });
1125 let _old_alloc_ptr = edits.as_ptr();
1126 let mut fold_edits = edits.into_iter();
1127
1128 if let Some(mut first_edit) = fold_edits.next() {
1129 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
1130 #[allow(clippy::filter_map_identity)]
1131 let mut v: Vec<_> = fold_edits
1132 .scan(&mut first_edit, |prev_edit, edit| {
1133 if prev_edit.old.end >= edit.old.start {
1134 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
1135 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
1136 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
1137 Some(None) // Skip this edit, it's merged
1138 } else {
1139 let prev = std::mem::replace(*prev_edit, edit);
1140 Some(Some(prev)) // Yield the previous edit
1141 }
1142 })
1143 .filter_map(|x| x)
1144 .collect();
1145 v.push(first_edit.clone());
1146 v
1147 } else {
1148 vec![]
1149 }
1150}
1151
1152#[derive(Clone, Debug, Default)]
1153struct Transform {
1154 summary: TransformSummary,
1155 placeholder: Option<TransformPlaceholder>,
1156}
1157
1158#[derive(Clone, Debug)]
1159struct TransformPlaceholder {
1160 text: SharedString,
1161 chars: u128,
1162 renderer: ChunkRenderer,
1163}
1164
1165impl Transform {
1166 fn is_fold(&self) -> bool {
1167 self.placeholder.is_some()
1168 }
1169}
1170
1171#[derive(Clone, Debug, Default, Eq, PartialEq)]
1172struct TransformSummary {
1173 output: MBTextSummary,
1174 input: MBTextSummary,
1175}
1176
1177impl sum_tree::Item for Transform {
1178 type Summary = TransformSummary;
1179
1180 fn summary(&self, _cx: ()) -> Self::Summary {
1181 self.summary.clone()
1182 }
1183}
1184
1185impl sum_tree::ContextLessSummary for TransformSummary {
1186 fn zero() -> Self {
1187 Default::default()
1188 }
1189
1190 fn add_summary(&mut self, other: &Self) {
1191 self.input += other.input;
1192 self.output += other.output;
1193 }
1194}
1195
1196#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)]
1197pub struct FoldId(pub(super) usize);
1198
1199impl From<FoldId> for ElementId {
1200 fn from(val: FoldId) -> Self {
1201 val.0.into()
1202 }
1203}
1204
1205#[derive(Clone, Debug, Eq, PartialEq)]
1206pub struct Fold {
1207 pub id: FoldId,
1208 pub range: FoldRange,
1209 pub placeholder: FoldPlaceholder,
1210}
1211
1212#[derive(Clone, Debug, Eq, PartialEq)]
1213pub struct FoldRange(pub(crate) Range<Anchor>);
1214
1215impl Deref for FoldRange {
1216 type Target = Range<Anchor>;
1217
1218 fn deref(&self) -> &Self::Target {
1219 &self.0
1220 }
1221}
1222
1223impl DerefMut for FoldRange {
1224 fn deref_mut(&mut self) -> &mut Self::Target {
1225 &mut self.0
1226 }
1227}
1228
1229impl Default for FoldRange {
1230 fn default() -> Self {
1231 Self(Anchor::Min..Anchor::Max)
1232 }
1233}
1234
1235#[derive(Clone, Debug)]
1236struct FoldMetadata {
1237 range: FoldRange,
1238 width: Option<Pixels>,
1239}
1240
1241impl sum_tree::Item for Fold {
1242 type Summary = FoldSummary;
1243
1244 fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
1245 FoldSummary {
1246 start: self.range.start,
1247 end: self.range.end,
1248 min_start: self.range.start,
1249 max_end: self.range.end,
1250 count: 1,
1251 }
1252 }
1253}
1254
1255#[derive(Clone, Debug)]
1256pub struct FoldSummary {
1257 start: Anchor,
1258 end: Anchor,
1259 min_start: Anchor,
1260 max_end: Anchor,
1261 count: usize,
1262}
1263
1264impl Default for FoldSummary {
1265 fn default() -> Self {
1266 Self {
1267 start: Anchor::Min,
1268 end: Anchor::Max,
1269 min_start: Anchor::Max,
1270 max_end: Anchor::Min,
1271 count: 0,
1272 }
1273 }
1274}
1275
1276impl sum_tree::Summary for FoldSummary {
1277 type Context<'a> = &'a MultiBufferSnapshot;
1278
1279 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1280 Default::default()
1281 }
1282
1283 fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
1284 if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
1285 self.min_start = other.min_start;
1286 }
1287 if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
1288 self.max_end = other.max_end;
1289 }
1290
1291 #[cfg(debug_assertions)]
1292 {
1293 let start_comparison = self.start.cmp(&other.start, buffer);
1294 assert!(start_comparison <= Ordering::Equal);
1295 if start_comparison == Ordering::Equal {
1296 assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
1297 }
1298 }
1299
1300 self.start = other.start;
1301 self.end = other.end;
1302 self.count += other.count;
1303 }
1304}
1305
1306impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
1307 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1308 Default::default()
1309 }
1310
1311 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1312 self.0.start = summary.start;
1313 self.0.end = summary.end;
1314 }
1315}
1316
1317impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
1318 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
1319 AnchorRangeExt::cmp(&self.0, &other.0, buffer)
1320 }
1321}
1322
1323impl<'a> sum_tree::Dimension<'a, FoldSummary> for MultiBufferOffset {
1324 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1325 Default::default()
1326 }
1327
1328 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1329 *self += summary.count;
1330 }
1331}
1332
1333#[derive(Clone)]
1334pub struct FoldRows<'a> {
1335 cursor: Cursor<'a, 'static, Transform, Dimensions<FoldPoint, InlayPoint>>,
1336 input_rows: InlayBufferRows<'a>,
1337 fold_point: FoldPoint,
1338}
1339
1340impl FoldRows<'_> {
1341 #[ztracing::instrument(skip_all)]
1342 pub(crate) fn seek(&mut self, row: u32) {
1343 let fold_point = FoldPoint::new(row, 0);
1344 self.cursor.seek(&fold_point, Bias::Left);
1345 let overshoot = fold_point.0 - self.cursor.start().0.0;
1346 let inlay_point = InlayPoint(self.cursor.start().1.0 + overshoot);
1347 self.input_rows.seek(inlay_point.row());
1348 self.fold_point = fold_point;
1349 }
1350}
1351
1352impl Iterator for FoldRows<'_> {
1353 type Item = RowInfo;
1354
1355 #[ztracing::instrument(skip_all)]
1356 fn next(&mut self) -> Option<Self::Item> {
1357 let mut traversed_fold = false;
1358 while self.fold_point > self.cursor.end().0 {
1359 self.cursor.next();
1360 traversed_fold = true;
1361 if self.cursor.item().is_none() {
1362 break;
1363 }
1364 }
1365
1366 if self.cursor.item().is_some() {
1367 if traversed_fold {
1368 self.input_rows.seek(self.cursor.start().1.0.row);
1369 self.input_rows.next();
1370 }
1371 *self.fold_point.row_mut() += 1;
1372 self.input_rows.next()
1373 } else {
1374 None
1375 }
1376 }
1377}
1378
1379/// A chunk of a buffer's text, along with its syntax highlight and
1380/// diagnostic status.
1381#[derive(Clone, Debug, Default)]
1382pub struct Chunk<'a> {
1383 /// The text of the chunk.
1384 pub text: &'a str,
1385 /// The syntax highlighting style of the chunk.
1386 pub syntax_highlight_id: Option<HighlightId>,
1387 /// The highlight style that has been applied to this chunk in
1388 /// the editor.
1389 pub highlight_style: Option<HighlightStyle>,
1390 /// The severity of diagnostic associated with this chunk, if any.
1391 pub diagnostic_severity: Option<lsp::DiagnosticSeverity>,
1392 /// Whether this chunk of text is marked as unnecessary.
1393 pub is_unnecessary: bool,
1394 /// Whether this chunk of text should be underlined.
1395 pub underline: bool,
1396 /// Whether this chunk of text was originally a tab character.
1397 pub is_tab: bool,
1398 /// Whether this chunk of text was originally a tab character.
1399 pub is_inlay: bool,
1400 /// An optional recipe for how the chunk should be presented.
1401 pub renderer: Option<ChunkRenderer>,
1402 /// Bitmap of tab character locations in chunk
1403 pub tabs: u128,
1404 /// Bitmap of character locations in chunk
1405 pub chars: u128,
1406 /// Bitmap of newline locations in chunk
1407 pub newlines: u128,
1408}
1409
1410#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1411pub enum ChunkRendererId {
1412 Fold(FoldId),
1413 Inlay(InlayId),
1414}
1415
1416/// A recipe for how the chunk should be presented.
1417#[derive(Clone)]
1418pub struct ChunkRenderer {
1419 /// The id of the renderer associated with this chunk.
1420 pub id: ChunkRendererId,
1421 /// Creates a custom element to represent this chunk.
1422 pub render: Arc<dyn Send + Sync + Fn(&mut ChunkRendererContext) -> AnyElement>,
1423 /// If true, the element is constrained to the shaped width of the text.
1424 pub constrain_width: bool,
1425 /// The width of the element, as measured during the last layout pass.
1426 ///
1427 /// This is None if the element has not been laid out yet.
1428 pub measured_width: Option<Pixels>,
1429}
1430
1431pub struct ChunkRendererContext<'a, 'b> {
1432 pub window: &'a mut Window,
1433 pub context: &'b mut App,
1434 pub max_width: Pixels,
1435}
1436
1437impl fmt::Debug for ChunkRenderer {
1438 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1439 f.debug_struct("ChunkRenderer")
1440 .field("constrain_width", &self.constrain_width)
1441 .finish()
1442 }
1443}
1444
1445impl Deref for ChunkRendererContext<'_, '_> {
1446 type Target = App;
1447
1448 fn deref(&self) -> &Self::Target {
1449 self.context
1450 }
1451}
1452
1453impl DerefMut for ChunkRendererContext<'_, '_> {
1454 fn deref_mut(&mut self) -> &mut Self::Target {
1455 self.context
1456 }
1457}
1458
1459pub struct FoldChunks<'a> {
1460 transform_cursor: Cursor<'a, 'static, Transform, Dimensions<FoldOffset, InlayOffset>>,
1461 inlay_chunks: InlayChunks<'a>,
1462 inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
1463 inlay_offset: InlayOffset,
1464 output_offset: FoldOffset,
1465 max_output_offset: FoldOffset,
1466}
1467
1468impl FoldChunks<'_> {
1469 #[ztracing::instrument(skip_all)]
1470 pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
1471 self.transform_cursor.seek(&range.start, Bias::Right);
1472
1473 let inlay_start = {
1474 let overshoot = range.start - self.transform_cursor.start().0;
1475 self.transform_cursor.start().1 + overshoot
1476 };
1477
1478 let transform_end = self.transform_cursor.end();
1479
1480 let inlay_end = if self
1481 .transform_cursor
1482 .item()
1483 .is_none_or(|transform| transform.is_fold())
1484 {
1485 inlay_start
1486 } else if range.end < transform_end.0 {
1487 let overshoot = range.end - self.transform_cursor.start().0;
1488 self.transform_cursor.start().1 + overshoot
1489 } else {
1490 transform_end.1
1491 };
1492
1493 self.inlay_chunks.seek(inlay_start..inlay_end);
1494 self.inlay_chunk = None;
1495 self.inlay_offset = inlay_start;
1496 self.output_offset = range.start;
1497 self.max_output_offset = range.end;
1498 }
1499}
1500
1501impl<'a> Iterator for FoldChunks<'a> {
1502 type Item = Chunk<'a>;
1503
1504 #[ztracing::instrument(skip_all)]
1505 fn next(&mut self) -> Option<Self::Item> {
1506 if self.output_offset >= self.max_output_offset {
1507 return None;
1508 }
1509
1510 let transform = self.transform_cursor.item()?;
1511
1512 // If we're in a fold, then return the fold's display text and
1513 // advance the transform and buffer cursors to the end of the fold.
1514 if let Some(placeholder) = transform.placeholder.as_ref() {
1515 self.inlay_chunk.take();
1516 self.inlay_offset += InlayOffset(transform.summary.input.len);
1517
1518 while self.inlay_offset >= self.transform_cursor.end().1
1519 && self.transform_cursor.item().is_some()
1520 {
1521 self.transform_cursor.next();
1522 }
1523
1524 self.output_offset.0 += placeholder.text.len();
1525 return Some(Chunk {
1526 text: &placeholder.text,
1527 chars: placeholder.chars,
1528 renderer: Some(placeholder.renderer.clone()),
1529 ..Default::default()
1530 });
1531 }
1532
1533 // When we reach a non-fold region, seek the underlying text
1534 // chunk iterator to the next unfolded range.
1535 if self.inlay_offset == self.transform_cursor.start().1
1536 && self.inlay_chunks.offset() != self.inlay_offset
1537 {
1538 let transform_start = self.transform_cursor.start();
1539 let transform_end = self.transform_cursor.end();
1540 let inlay_end = if self.max_output_offset < transform_end.0 {
1541 let overshoot = self.max_output_offset - transform_start.0;
1542 transform_start.1 + overshoot
1543 } else {
1544 transform_end.1
1545 };
1546
1547 self.inlay_chunks.seek(self.inlay_offset..inlay_end);
1548 }
1549
1550 // Retrieve a chunk from the current location in the buffer.
1551 if self.inlay_chunk.is_none() {
1552 let chunk_offset = self.inlay_chunks.offset();
1553 self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
1554 }
1555
1556 // Otherwise, take a chunk from the buffer's text.
1557 if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
1558 let chunk = &mut inlay_chunk.chunk;
1559 let buffer_chunk_end = buffer_chunk_start + chunk.text.len();
1560 let transform_end = self.transform_cursor.end().1;
1561 let chunk_end = buffer_chunk_end.min(transform_end);
1562
1563 let bit_start = self.inlay_offset - buffer_chunk_start;
1564 let bit_end = chunk_end - buffer_chunk_start;
1565 chunk.text = &chunk.text[bit_start..bit_end];
1566
1567 let bit_end = chunk_end - buffer_chunk_start;
1568 let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
1569
1570 chunk.tabs = (chunk.tabs >> bit_start) & mask;
1571 chunk.chars = (chunk.chars >> bit_start) & mask;
1572 chunk.newlines = (chunk.newlines >> bit_start) & mask;
1573
1574 if chunk_end == transform_end {
1575 self.transform_cursor.next();
1576 } else if chunk_end == buffer_chunk_end {
1577 self.inlay_chunk.take();
1578 }
1579
1580 self.inlay_offset = chunk_end;
1581 self.output_offset.0 += chunk.text.len();
1582 return Some(Chunk {
1583 text: chunk.text,
1584 tabs: chunk.tabs,
1585 chars: chunk.chars,
1586 newlines: chunk.newlines,
1587 syntax_highlight_id: chunk.syntax_highlight_id,
1588 highlight_style: chunk.highlight_style,
1589 diagnostic_severity: chunk.diagnostic_severity,
1590 is_unnecessary: chunk.is_unnecessary,
1591 is_tab: chunk.is_tab,
1592 is_inlay: chunk.is_inlay,
1593 underline: chunk.underline,
1594 renderer: inlay_chunk.renderer,
1595 });
1596 }
1597
1598 None
1599 }
1600}
1601
1602#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
1603pub struct FoldOffset(pub MultiBufferOffset);
1604
1605impl FoldOffset {
1606 #[ztracing::instrument(skip_all)]
1607 pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
1608 let (start, _, item) = snapshot
1609 .transforms
1610 .find::<Dimensions<FoldOffset, TransformSummary>, _>((), &self, Bias::Right);
1611 let overshoot = if item.is_none_or(|t| t.is_fold()) {
1612 Point::new(0, (self.0 - start.0.0) as u32)
1613 } else {
1614 let inlay_offset = start.1.input.len + (self - start.0);
1615 let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
1616 inlay_point.0 - start.1.input.lines
1617 };
1618 FoldPoint(start.1.output.lines + overshoot)
1619 }
1620
1621 #[cfg(test)]
1622 #[ztracing::instrument(skip_all)]
1623 pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
1624 let (start, _, _) = snapshot
1625 .transforms
1626 .find::<Dimensions<FoldOffset, InlayOffset>, _>((), &self, Bias::Right);
1627 let overshoot = self - start.0;
1628 InlayOffset(start.1.0 + overshoot)
1629 }
1630}
1631
1632impl Add for FoldOffset {
1633 type Output = Self;
1634
1635 fn add(self, rhs: Self) -> Self::Output {
1636 Self(self.0 + rhs.0)
1637 }
1638}
1639
1640impl Sub for FoldOffset {
1641 type Output = <MultiBufferOffset as Sub>::Output;
1642
1643 fn sub(self, rhs: Self) -> Self::Output {
1644 self.0 - rhs.0
1645 }
1646}
1647
1648impl<T> SubAssign<T> for FoldOffset
1649where
1650 MultiBufferOffset: SubAssign<T>,
1651{
1652 fn sub_assign(&mut self, rhs: T) {
1653 self.0 -= rhs;
1654 }
1655}
1656
1657impl<T> Add<T> for FoldOffset
1658where
1659 MultiBufferOffset: Add<T, Output = MultiBufferOffset>,
1660{
1661 type Output = Self;
1662
1663 fn add(self, rhs: T) -> Self::Output {
1664 Self(self.0 + rhs)
1665 }
1666}
1667
1668impl AddAssign for FoldOffset {
1669 fn add_assign(&mut self, rhs: Self) {
1670 self.0 += rhs.0;
1671 }
1672}
1673
1674impl<T> AddAssign<T> for FoldOffset
1675where
1676 MultiBufferOffset: AddAssign<T>,
1677{
1678 fn add_assign(&mut self, rhs: T) {
1679 self.0 += rhs;
1680 }
1681}
1682
1683impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1684 fn zero(_cx: ()) -> Self {
1685 Default::default()
1686 }
1687
1688 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1689 self.0 += summary.output.len;
1690 }
1691}
1692
1693impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
1694 fn zero(_cx: ()) -> Self {
1695 Default::default()
1696 }
1697
1698 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1699 self.0 += &summary.input.lines;
1700 }
1701}
1702
1703impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
1704 fn zero(_cx: ()) -> Self {
1705 Default::default()
1706 }
1707
1708 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1709 self.0 += summary.input.len;
1710 }
1711}
1712
1713pub type FoldEdit = Edit<FoldOffset>;
1714
1715#[cfg(test)]
1716mod tests {
1717 use super::*;
1718 use crate::{MultiBuffer, ToPoint, display_map::inlay_map::InlayMap};
1719 use Bias::{Left, Right};
1720 use collections::HashSet;
1721 use rand::prelude::*;
1722 use settings::SettingsStore;
1723 use std::{env, mem};
1724 use text::Patch;
1725 use util::RandomCharIter;
1726 use util::test::sample_text;
1727
1728 #[gpui::test]
1729 fn test_basic_folds(cx: &mut gpui::App) {
1730 init_test(cx);
1731 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1732 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1733 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1734 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1735 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1736
1737 let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
1738 let (snapshot2, edits) = writer.fold(vec![
1739 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1740 (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
1741 ]);
1742 assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
1743 assert_eq!(
1744 edits,
1745 &[
1746 FoldEdit {
1747 old: FoldOffset(MultiBufferOffset(2))..FoldOffset(MultiBufferOffset(16)),
1748 new: FoldOffset(MultiBufferOffset(2))..FoldOffset(MultiBufferOffset(5)),
1749 },
1750 FoldEdit {
1751 old: FoldOffset(MultiBufferOffset(18))..FoldOffset(MultiBufferOffset(29)),
1752 new: FoldOffset(MultiBufferOffset(7))..FoldOffset(MultiBufferOffset(10)),
1753 },
1754 ]
1755 );
1756
1757 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1758 buffer.edit(
1759 vec![
1760 (Point::new(0, 0)..Point::new(0, 1), "123"),
1761 (Point::new(2, 3)..Point::new(2, 3), "123"),
1762 ],
1763 None,
1764 cx,
1765 );
1766 buffer.snapshot(cx)
1767 });
1768
1769 let (inlay_snapshot, inlay_edits) =
1770 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1771 let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
1772 assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
1773 assert_eq!(
1774 edits,
1775 &[
1776 FoldEdit {
1777 old: FoldOffset(MultiBufferOffset(0))..FoldOffset(MultiBufferOffset(1)),
1778 new: FoldOffset(MultiBufferOffset(0))..FoldOffset(MultiBufferOffset(3)),
1779 },
1780 FoldEdit {
1781 old: FoldOffset(MultiBufferOffset(6))..FoldOffset(MultiBufferOffset(6)),
1782 new: FoldOffset(MultiBufferOffset(8))..FoldOffset(MultiBufferOffset(11)),
1783 },
1784 ]
1785 );
1786
1787 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1788 buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
1789 buffer.snapshot(cx)
1790 });
1791 let (inlay_snapshot, inlay_edits) =
1792 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1793 let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
1794 assert_eq!(snapshot4.text(), "123a⋯c123456eee");
1795
1796 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1797 writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false);
1798 let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
1799 assert_eq!(snapshot5.text(), "123a⋯c123456eee");
1800
1801 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1802 writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true);
1803 let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
1804 assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1805 }
1806
1807 #[gpui::test]
1808 fn test_adjacent_folds(cx: &mut gpui::App) {
1809 init_test(cx);
1810 let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
1811 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1812 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1813 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1814
1815 {
1816 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1817
1818 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1819 writer.fold(vec![(
1820 MultiBufferOffset(5)..MultiBufferOffset(8),
1821 FoldPlaceholder::test(),
1822 )]);
1823 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1824 assert_eq!(snapshot.text(), "abcde⋯ijkl");
1825
1826 // Create an fold adjacent to the start of the first fold.
1827 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1828 writer.fold(vec![
1829 (
1830 MultiBufferOffset(0)..MultiBufferOffset(1),
1831 FoldPlaceholder::test(),
1832 ),
1833 (
1834 MultiBufferOffset(2)..MultiBufferOffset(5),
1835 FoldPlaceholder::test(),
1836 ),
1837 ]);
1838 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1839 assert_eq!(snapshot.text(), "⋯b⋯ijkl");
1840
1841 // Create an fold adjacent to the end of the first fold.
1842 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1843 writer.fold(vec![
1844 (
1845 MultiBufferOffset(11)..MultiBufferOffset(11),
1846 FoldPlaceholder::test(),
1847 ),
1848 (
1849 MultiBufferOffset(8)..MultiBufferOffset(10),
1850 FoldPlaceholder::test(),
1851 ),
1852 ]);
1853 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1854 assert_eq!(snapshot.text(), "⋯b⋯kl");
1855 }
1856
1857 {
1858 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1859
1860 // Create two adjacent folds.
1861 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1862 writer.fold(vec![
1863 (
1864 MultiBufferOffset(0)..MultiBufferOffset(2),
1865 FoldPlaceholder::test(),
1866 ),
1867 (
1868 MultiBufferOffset(2)..MultiBufferOffset(5),
1869 FoldPlaceholder::test(),
1870 ),
1871 ]);
1872 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1873 assert_eq!(snapshot.text(), "⋯fghijkl");
1874
1875 // Edit within one of the folds.
1876 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1877 buffer.edit(
1878 [(MultiBufferOffset(0)..MultiBufferOffset(1), "12345")],
1879 None,
1880 cx,
1881 );
1882 buffer.snapshot(cx)
1883 });
1884 let (inlay_snapshot, inlay_edits) =
1885 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1886 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1887 assert_eq!(snapshot.text(), "12345⋯fghijkl");
1888 }
1889 }
1890
1891 #[gpui::test]
1892 fn test_overlapping_folds(cx: &mut gpui::App) {
1893 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1894 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1895 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1896 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1897 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1898 writer.fold(vec![
1899 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1900 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1901 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1902 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1903 ]);
1904 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1905 assert_eq!(snapshot.text(), "aa⋯eeeee");
1906 }
1907
1908 #[gpui::test]
1909 fn test_merging_folds_via_edit(cx: &mut gpui::App) {
1910 init_test(cx);
1911 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1912 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1913 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1914 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1915 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1916
1917 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1918 writer.fold(vec![
1919 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1920 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1921 ]);
1922 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1923 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
1924
1925 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1926 buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
1927 buffer.snapshot(cx)
1928 });
1929 let (inlay_snapshot, inlay_edits) =
1930 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1931 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1932 assert_eq!(snapshot.text(), "aa⋯eeeee");
1933 }
1934
1935 #[gpui::test]
1936 fn test_folds_in_range(cx: &mut gpui::App) {
1937 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1938 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1939 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1940 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1941
1942 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1943 writer.fold(vec![
1944 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1945 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1946 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1947 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1948 ]);
1949 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1950 let fold_ranges = snapshot
1951 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1952 .map(|fold| {
1953 fold.range.start.to_point(&buffer_snapshot)
1954 ..fold.range.end.to_point(&buffer_snapshot)
1955 })
1956 .collect::<Vec<_>>();
1957 assert_eq!(
1958 fold_ranges,
1959 vec![
1960 Point::new(0, 2)..Point::new(2, 2),
1961 Point::new(1, 2)..Point::new(3, 2)
1962 ]
1963 );
1964 }
1965
1966 #[gpui::test(iterations = 100)]
1967 fn test_random_folds(cx: &mut gpui::App, mut rng: StdRng) {
1968 init_test(cx);
1969 let operations = env::var("OPERATIONS")
1970 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1971 .unwrap_or(10);
1972
1973 let len = rng.random_range(0..10);
1974 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1975 let buffer = if rng.random() {
1976 MultiBuffer::build_simple(&text, cx)
1977 } else {
1978 MultiBuffer::build_random(&mut rng, cx)
1979 };
1980 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1981 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1982 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1983
1984 let (mut initial_snapshot, _) = map.read(inlay_snapshot, vec![]);
1985 let mut snapshot_edits = Vec::new();
1986
1987 let mut next_inlay_id = 0;
1988 for _ in 0..operations {
1989 log::info!("text: {:?}", buffer_snapshot.text());
1990 let mut buffer_edits = Vec::new();
1991 let mut inlay_edits = Vec::new();
1992 match rng.random_range(0..=100) {
1993 0..=39 => {
1994 snapshot_edits.extend(map.randomly_mutate(&mut rng));
1995 }
1996 40..=59 => {
1997 let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1998 inlay_edits = edits;
1999 }
2000 _ => buffer.update(cx, |buffer, cx| {
2001 let subscription = buffer.subscribe();
2002 let edit_count = rng.random_range(1..=5);
2003 buffer.randomly_mutate(&mut rng, edit_count, cx);
2004 buffer_snapshot = buffer.snapshot(cx);
2005 let edits = subscription.consume().into_inner();
2006 log::info!("editing {:?}", edits);
2007 buffer_edits.extend(edits);
2008 }),
2009 };
2010
2011 let (inlay_snapshot, new_inlay_edits) =
2012 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
2013 log::info!("inlay text {:?}", inlay_snapshot.text());
2014
2015 let inlay_edits = Patch::new(inlay_edits)
2016 .compose(new_inlay_edits)
2017 .into_inner();
2018 let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
2019 snapshot_edits.push((snapshot.clone(), edits));
2020
2021 let mut expected_text: String = inlay_snapshot.text().to_string();
2022 for fold_range in map.merged_folds().into_iter().rev() {
2023 let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
2024 let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
2025 expected_text.replace_range(fold_inlay_start.0.0..fold_inlay_end.0.0, "⋯");
2026 }
2027
2028 assert_eq!(snapshot.text(), expected_text);
2029 log::info!(
2030 "fold text {:?} ({} lines)",
2031 expected_text,
2032 expected_text.matches('\n').count() + 1
2033 );
2034
2035 let mut prev_row = 0;
2036 let mut expected_buffer_rows = Vec::new();
2037 for fold_range in map.merged_folds() {
2038 let fold_start = inlay_snapshot
2039 .to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
2040 .row();
2041 let fold_end = inlay_snapshot
2042 .to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
2043 .row();
2044 expected_buffer_rows.extend(
2045 inlay_snapshot
2046 .row_infos(prev_row)
2047 .take((1 + fold_start - prev_row) as usize),
2048 );
2049 prev_row = 1 + fold_end;
2050 }
2051 expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
2052
2053 assert_eq!(
2054 expected_buffer_rows.len(),
2055 expected_text.matches('\n').count() + 1,
2056 "wrong expected buffer rows {:?}. text: {:?}",
2057 expected_buffer_rows,
2058 expected_text
2059 );
2060
2061 for (output_row, line) in expected_text.lines().enumerate() {
2062 let line_len = snapshot.line_len(output_row as u32);
2063 assert_eq!(line_len, line.len() as u32);
2064 }
2065
2066 let longest_row = snapshot.longest_row();
2067 let longest_char_column = expected_text
2068 .split('\n')
2069 .nth(longest_row as usize)
2070 .unwrap()
2071 .chars()
2072 .count();
2073 let mut fold_point = FoldPoint::new(0, 0);
2074 let mut fold_offset = FoldOffset(MultiBufferOffset(0));
2075 let mut char_column = 0;
2076 for c in expected_text.chars() {
2077 let inlay_point = fold_point.to_inlay_point(&snapshot);
2078 let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
2079 assert_eq!(
2080 snapshot.to_fold_point(inlay_point, Right),
2081 fold_point,
2082 "{:?} -> fold point",
2083 inlay_point,
2084 );
2085 assert_eq!(
2086 inlay_snapshot.to_offset(inlay_point),
2087 inlay_offset,
2088 "inlay_snapshot.to_offset({:?})",
2089 inlay_point,
2090 );
2091 assert_eq!(
2092 fold_point.to_offset(&snapshot),
2093 fold_offset,
2094 "fold_point.to_offset({:?})",
2095 fold_point,
2096 );
2097
2098 if c == '\n' {
2099 *fold_point.row_mut() += 1;
2100 *fold_point.column_mut() = 0;
2101 char_column = 0;
2102 } else {
2103 *fold_point.column_mut() += c.len_utf8() as u32;
2104 char_column += 1;
2105 }
2106 fold_offset.0 += c.len_utf8();
2107 if char_column > longest_char_column {
2108 panic!(
2109 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
2110 longest_row,
2111 longest_char_column,
2112 fold_point.row(),
2113 char_column
2114 );
2115 }
2116 }
2117
2118 for _ in 0..5 {
2119 let mut start = snapshot.clip_offset(
2120 FoldOffset(rng.random_range(MultiBufferOffset(0)..=snapshot.len().0)),
2121 Bias::Left,
2122 );
2123 let mut end = snapshot.clip_offset(
2124 FoldOffset(rng.random_range(MultiBufferOffset(0)..=snapshot.len().0)),
2125 Bias::Right,
2126 );
2127 if start > end {
2128 mem::swap(&mut start, &mut end);
2129 }
2130
2131 let text = &expected_text[start.0.0..end.0.0];
2132 assert_eq!(
2133 snapshot
2134 .chunks(start..end, false, Highlights::default())
2135 .map(|c| c.text)
2136 .collect::<String>(),
2137 text,
2138 );
2139 }
2140
2141 let mut fold_row = 0;
2142 while fold_row < expected_buffer_rows.len() as u32 {
2143 assert_eq!(
2144 snapshot.row_infos(fold_row).collect::<Vec<_>>(),
2145 expected_buffer_rows[(fold_row as usize)..],
2146 "wrong buffer rows starting at fold row {}",
2147 fold_row,
2148 );
2149 fold_row += 1;
2150 }
2151
2152 let folded_buffer_rows = map
2153 .merged_folds()
2154 .iter()
2155 .flat_map(|fold_range| {
2156 let start_row = fold_range.start.to_point(&buffer_snapshot).row;
2157 let end = fold_range.end.to_point(&buffer_snapshot);
2158 if end.column == 0 {
2159 start_row..end.row
2160 } else {
2161 start_row..end.row + 1
2162 }
2163 })
2164 .collect::<HashSet<_>>();
2165 for row in 0..=buffer_snapshot.max_point().row {
2166 assert_eq!(
2167 snapshot.is_line_folded(MultiBufferRow(row)),
2168 folded_buffer_rows.contains(&row),
2169 "expected buffer row {}{} to be folded",
2170 row,
2171 if folded_buffer_rows.contains(&row) {
2172 ""
2173 } else {
2174 " not"
2175 }
2176 );
2177 }
2178
2179 for _ in 0..5 {
2180 let end = buffer_snapshot.clip_offset(
2181 rng.random_range(MultiBufferOffset(0)..=buffer_snapshot.len()),
2182 Right,
2183 );
2184 let start =
2185 buffer_snapshot.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2186 let expected_folds = map
2187 .snapshot
2188 .folds
2189 .items(&buffer_snapshot)
2190 .into_iter()
2191 .filter(|fold| {
2192 let start = buffer_snapshot.anchor_before(start);
2193 let end = buffer_snapshot.anchor_after(end);
2194 start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
2195 && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
2196 })
2197 .collect::<Vec<_>>();
2198
2199 assert_eq!(
2200 snapshot
2201 .folds_in_range(start..end)
2202 .cloned()
2203 .collect::<Vec<_>>(),
2204 expected_folds
2205 );
2206 }
2207
2208 let text = snapshot.text();
2209 for _ in 0..5 {
2210 let start_row = rng.random_range(0..=snapshot.max_point().row());
2211 let start_column = rng.random_range(0..=snapshot.line_len(start_row));
2212 let end_row = rng.random_range(0..=snapshot.max_point().row());
2213 let end_column = rng.random_range(0..=snapshot.line_len(end_row));
2214 let mut start =
2215 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
2216 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
2217 if start > end {
2218 mem::swap(&mut start, &mut end);
2219 }
2220
2221 let lines = start..end;
2222 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
2223 assert_eq!(
2224 snapshot.text_summary_for_range(lines),
2225 MBTextSummary::from(&text[bytes.start.0.0..bytes.end.0.0])
2226 )
2227 }
2228
2229 let mut text = initial_snapshot.text();
2230 for (snapshot, edits) in snapshot_edits.drain(..) {
2231 let new_text = snapshot.text();
2232 for edit in edits {
2233 let old_bytes = edit.new.start.0.0..edit.new.start.0.0 + edit.old_len();
2234 let new_bytes = edit.new.start.0.0..edit.new.end.0.0;
2235 text.replace_range(old_bytes, &new_text[new_bytes]);
2236 }
2237
2238 assert_eq!(text, new_text);
2239 initial_snapshot = snapshot;
2240 }
2241 }
2242 }
2243
2244 #[gpui::test]
2245 fn test_buffer_rows(cx: &mut gpui::App) {
2246 let text = sample_text(6, 6, 'a') + "\n";
2247 let buffer = MultiBuffer::build_simple(&text, cx);
2248
2249 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2250 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2251 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
2252
2253 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
2254 writer.fold(vec![
2255 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
2256 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
2257 ]);
2258
2259 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
2260 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
2261 assert_eq!(
2262 snapshot
2263 .row_infos(0)
2264 .map(|info| info.buffer_row)
2265 .collect::<Vec<_>>(),
2266 [Some(0), Some(3), Some(5), Some(6)]
2267 );
2268 assert_eq!(
2269 snapshot
2270 .row_infos(3)
2271 .map(|info| info.buffer_row)
2272 .collect::<Vec<_>>(),
2273 [Some(6)]
2274 );
2275 }
2276
2277 #[gpui::test(iterations = 100)]
2278 fn test_random_chunk_bitmaps(cx: &mut gpui::App, mut rng: StdRng) {
2279 init_test(cx);
2280
2281 // Generate random buffer using existing test infrastructure
2282 let text_len = rng.random_range(0..10000);
2283 let buffer = if rng.random() {
2284 let text = RandomCharIter::new(&mut rng)
2285 .take(text_len)
2286 .collect::<String>();
2287 MultiBuffer::build_simple(&text, cx)
2288 } else {
2289 MultiBuffer::build_random(&mut rng, cx)
2290 };
2291 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2292 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2293 let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
2294
2295 // Perform random mutations
2296 let mutation_count = rng.random_range(1..10);
2297 for _ in 0..mutation_count {
2298 fold_map.randomly_mutate(&mut rng);
2299 }
2300
2301 let (snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
2302
2303 // Get all chunks and verify their bitmaps
2304 let chunks = snapshot.chunks(
2305 FoldOffset(MultiBufferOffset(0))..FoldOffset(snapshot.len().0),
2306 false,
2307 Highlights::default(),
2308 );
2309
2310 for chunk in chunks {
2311 let chunk_text = chunk.text;
2312 let chars_bitmap = chunk.chars;
2313 let tabs_bitmap = chunk.tabs;
2314
2315 // Check empty chunks have empty bitmaps
2316 if chunk_text.is_empty() {
2317 assert_eq!(
2318 chars_bitmap, 0,
2319 "Empty chunk should have empty chars bitmap"
2320 );
2321 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
2322 continue;
2323 }
2324
2325 // Verify that chunk text doesn't exceed 128 bytes
2326 assert!(
2327 chunk_text.len() <= 128,
2328 "Chunk text length {} exceeds 128 bytes",
2329 chunk_text.len()
2330 );
2331
2332 // Verify chars bitmap
2333 let char_indices = chunk_text
2334 .char_indices()
2335 .map(|(i, _)| i)
2336 .collect::<Vec<_>>();
2337
2338 for byte_idx in 0..chunk_text.len() {
2339 let should_have_bit = char_indices.contains(&byte_idx);
2340 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
2341
2342 if has_bit != should_have_bit {
2343 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2344 eprintln!("Char indices: {:?}", char_indices);
2345 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
2346 assert_eq!(
2347 has_bit, should_have_bit,
2348 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
2349 byte_idx, chunk_text, should_have_bit, has_bit
2350 );
2351 }
2352 }
2353
2354 // Verify tabs bitmap
2355 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
2356 let is_tab = byte == b'\t';
2357 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
2358
2359 assert_eq!(
2360 has_bit, is_tab,
2361 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
2362 byte_idx, chunk_text, byte as char, is_tab, has_bit
2363 );
2364 }
2365 }
2366 }
2367
2368 fn init_test(cx: &mut gpui::App) {
2369 let store = SettingsStore::test(cx);
2370 cx.set_global(store);
2371 }
2372
2373 impl FoldMap {
2374 fn merged_folds(&self) -> Vec<Range<MultiBufferOffset>> {
2375 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2376 let buffer = &inlay_snapshot.buffer;
2377 let mut folds = self.snapshot.folds.items(buffer);
2378 // Ensure sorting doesn't change how folds get merged and displayed.
2379 folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
2380 let mut folds = folds
2381 .iter()
2382 .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
2383 .peekable();
2384
2385 let mut merged_folds = Vec::new();
2386 while let Some(mut fold_range) = folds.next() {
2387 while let Some(next_range) = folds.peek() {
2388 if fold_range.end >= next_range.start {
2389 if next_range.end > fold_range.end {
2390 fold_range.end = next_range.end;
2391 }
2392 folds.next();
2393 } else {
2394 break;
2395 }
2396 }
2397 if fold_range.end > fold_range.start {
2398 merged_folds.push(fold_range);
2399 }
2400 }
2401 merged_folds
2402 }
2403
2404 pub fn randomly_mutate(
2405 &mut self,
2406 rng: &mut impl Rng,
2407 ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
2408 let mut snapshot_edits = Vec::new();
2409 match rng.random_range(0..=100) {
2410 0..=39 if !self.snapshot.folds.is_empty() => {
2411 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2412 let buffer = &inlay_snapshot.buffer;
2413 let mut to_unfold = Vec::new();
2414 for _ in 0..rng.random_range(1..=3) {
2415 let end = buffer.clip_offset(
2416 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2417 Right,
2418 );
2419 let start =
2420 buffer.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2421 to_unfold.push(start..end);
2422 }
2423 let inclusive = rng.random();
2424 log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
2425 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
2426 snapshot_edits.push((snapshot, edits));
2427 let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive);
2428 snapshot_edits.push((snapshot, edits));
2429 }
2430 _ => {
2431 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2432 let buffer = &inlay_snapshot.buffer;
2433 let mut to_fold = Vec::new();
2434 for _ in 0..rng.random_range(1..=2) {
2435 let end = buffer.clip_offset(
2436 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
2437 Right,
2438 );
2439 let start =
2440 buffer.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
2441 to_fold.push((start..end, FoldPlaceholder::test()));
2442 }
2443 log::info!("folding {:?}", to_fold);
2444 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
2445 snapshot_edits.push((snapshot, edits));
2446 let (snapshot, edits) = writer.fold(to_fold);
2447 snapshot_edits.push((snapshot, edits));
2448 }
2449 }
2450 snapshot_edits
2451 }
2452 }
2453}