Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:36:36.757Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

fold_map.rs

2483 lines · 90.7 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.