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