1use collections::HashMap;
  2use gpui::{AnyElement, IntoElement};
  3use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
  4use serde::{Deserialize, Serialize};
  5use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
  6use sum_tree::{Bias, SeekTarget, SumTree};
  7use text::Point;
  8use ui::{App, SharedString, Window};
  9
 10use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
 11
 12#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
 13pub struct CreaseId(usize);
 14
 15pub struct CreaseMap {
 16    snapshot: CreaseSnapshot,
 17    next_id: CreaseId,
 18    id_to_range: HashMap<CreaseId, Range<Anchor>>,
 19}
 20
 21impl CreaseMap {
 22    pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
 23        CreaseMap {
 24            snapshot: CreaseSnapshot::new(snapshot),
 25            next_id: CreaseId::default(),
 26            id_to_range: HashMap::default(),
 27        }
 28    }
 29}
 30
 31#[derive(Clone)]
 32pub struct CreaseSnapshot {
 33    creases: SumTree<CreaseItem>,
 34}
 35
 36impl CreaseSnapshot {
 37    pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
 38        CreaseSnapshot {
 39            creases: SumTree::new(snapshot),
 40        }
 41    }
 42
 43    pub fn creases(&self) -> impl Iterator<Item = (CreaseId, &Crease<Anchor>)> {
 44        self.creases.iter().map(|item| (item.id, &item.crease))
 45    }
 46
 47    /// Returns the first Crease starting on the specified buffer row.
 48    pub fn query_row<'a>(
 49        &'a self,
 50        row: MultiBufferRow,
 51        snapshot: &'a MultiBufferSnapshot,
 52    ) -> Option<&'a Crease<Anchor>> {
 53        let start = snapshot.anchor_before(Point::new(row.0, 0));
 54        let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
 55        cursor.seek(&start, Bias::Left);
 56        while let Some(item) = cursor.item() {
 57            match Ord::cmp(&item.crease.range().start.to_point(snapshot).row, &row.0) {
 58                Ordering::Less => cursor.next(),
 59                Ordering::Equal => {
 60                    if item.crease.range().start.is_valid(snapshot) {
 61                        return Some(&item.crease);
 62                    } else {
 63                        cursor.next();
 64                    }
 65                }
 66                Ordering::Greater => break,
 67            }
 68        }
 69        None
 70    }
 71
 72    pub fn creases_in_range<'a>(
 73        &'a self,
 74        range: Range<MultiBufferRow>,
 75        snapshot: &'a MultiBufferSnapshot,
 76    ) -> impl 'a + Iterator<Item = &'a Crease<Anchor>> {
 77        let start = snapshot.anchor_before(Point::new(range.start.0, 0));
 78        let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
 79        cursor.seek(&start, Bias::Left);
 80
 81        std::iter::from_fn(move || {
 82            while let Some(item) = cursor.item() {
 83                cursor.next();
 84                let crease_range = item.crease.range();
 85                let crease_start = crease_range.start.to_point(snapshot);
 86                let crease_end = crease_range.end.to_point(snapshot);
 87                if crease_end.row > range.end.0 {
 88                    continue;
 89                }
 90                if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
 91                    return Some(&item.crease);
 92                }
 93            }
 94            None
 95        })
 96    }
 97
 98    pub fn crease_items_with_offsets(
 99        &self,
100        snapshot: &MultiBufferSnapshot,
101    ) -> Vec<(CreaseId, Range<Point>)> {
102        let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
103        let mut results = Vec::new();
104
105        cursor.next();
106        while let Some(item) = cursor.item() {
107            let crease_range = item.crease.range();
108            let start_point = crease_range.start.to_point(snapshot);
109            let end_point = crease_range.end.to_point(snapshot);
110            results.push((item.id, start_point..end_point));
111            cursor.next();
112        }
113
114        results
115    }
116}
117
118type RenderToggleFn = Arc<
119    dyn Send
120        + Sync
121        + Fn(
122            MultiBufferRow,
123            bool,
124            Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut App)>,
125            &mut Window,
126            &mut App,
127        ) -> AnyElement,
128>;
129type RenderTrailerFn =
130    Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut Window, &mut App) -> AnyElement>;
131
132#[derive(Clone)]
133pub enum Crease<T> {
134    Inline {
135        range: Range<T>,
136        placeholder: FoldPlaceholder,
137        render_toggle: Option<RenderToggleFn>,
138        render_trailer: Option<RenderTrailerFn>,
139        metadata: Option<CreaseMetadata>,
140    },
141    Block {
142        range: Range<T>,
143        block_height: u32,
144        block_style: BlockStyle,
145        render_block: RenderBlock,
146        block_priority: usize,
147        render_toggle: Option<RenderToggleFn>,
148    },
149}
150
151/// Metadata about a [`Crease`], that is used for serialization.
152#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
153pub struct CreaseMetadata {
154    pub icon_path: SharedString,
155    pub label: SharedString,
156}
157
158impl<T> Crease<T> {
159    pub fn simple(range: Range<T>, placeholder: FoldPlaceholder) -> Self {
160        Crease::Inline {
161            range,
162            placeholder,
163            render_toggle: None,
164            render_trailer: None,
165            metadata: None,
166        }
167    }
168
169    pub fn block(range: Range<T>, height: u32, style: BlockStyle, render: RenderBlock) -> Self {
170        Self::Block {
171            range,
172            block_height: height,
173            block_style: style,
174            render_block: render,
175            block_priority: 0,
176            render_toggle: None,
177        }
178    }
179
180    pub fn inline<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
181        range: Range<T>,
182        placeholder: FoldPlaceholder,
183        render_toggle: RenderToggle,
184        render_trailer: RenderTrailer,
185    ) -> Self
186    where
187        RenderToggle: 'static
188            + Send
189            + Sync
190            + Fn(
191                MultiBufferRow,
192                bool,
193                Arc<dyn Send + Sync + Fn(bool, &mut Window, &mut App)>,
194                &mut Window,
195                &mut App,
196            ) -> ToggleElement
197            + 'static,
198        ToggleElement: IntoElement,
199        RenderTrailer: 'static
200            + Send
201            + Sync
202            + Fn(MultiBufferRow, bool, &mut Window, &mut App) -> TrailerElement
203            + 'static,
204        TrailerElement: IntoElement,
205    {
206        Crease::Inline {
207            range,
208            placeholder,
209            render_toggle: Some(Arc::new(move |row, folded, toggle, window, cx| {
210                render_toggle(row, folded, toggle, window, cx).into_any_element()
211            })),
212            render_trailer: Some(Arc::new(move |row, folded, window, cx| {
213                render_trailer(row, folded, window, cx).into_any_element()
214            })),
215            metadata: None,
216        }
217    }
218
219    pub fn with_metadata(self, metadata: CreaseMetadata) -> Self {
220        match self {
221            Crease::Inline {
222                range,
223                placeholder,
224                render_toggle,
225                render_trailer,
226                ..
227            } => Crease::Inline {
228                range,
229                placeholder,
230                render_toggle,
231                render_trailer,
232                metadata: Some(metadata),
233            },
234            Crease::Block { .. } => self,
235        }
236    }
237
238    pub const fn range(&self) -> &Range<T> {
239        match self {
240            Crease::Inline { range, .. } => range,
241            Crease::Block { range, .. } => range,
242        }
243    }
244
245    pub const fn metadata(&self) -> Option<&CreaseMetadata> {
246        match self {
247            Self::Inline { metadata, .. } => metadata.as_ref(),
248            Self::Block { .. } => None,
249        }
250    }
251}
252
253impl<T> std::fmt::Debug for Crease<T>
254where
255    T: Debug,
256{
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        match self {
259            Crease::Inline {
260                range, metadata, ..
261            } => f
262                .debug_struct("Crease::Inline")
263                .field("range", range)
264                .field("metadata", metadata)
265                .finish_non_exhaustive(),
266            Crease::Block {
267                range,
268                block_height,
269                ..
270            } => f
271                .debug_struct("Crease::Block")
272                .field("range", range)
273                .field("height", block_height)
274                .finish_non_exhaustive(),
275        }
276    }
277}
278
279#[derive(Clone, Debug)]
280struct CreaseItem {
281    id: CreaseId,
282    crease: Crease<Anchor>,
283}
284
285impl CreaseMap {
286    pub fn snapshot(&self) -> CreaseSnapshot {
287        self.snapshot.clone()
288    }
289
290    pub fn insert(
291        &mut self,
292        creases: impl IntoIterator<Item = Crease<Anchor>>,
293        snapshot: &MultiBufferSnapshot,
294    ) -> Vec<CreaseId> {
295        let mut new_ids = Vec::new();
296        self.snapshot.creases = {
297            let mut new_creases = SumTree::new(snapshot);
298            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
299            for crease in creases {
300                let crease_range = crease.range().clone();
301                new_creases.append(cursor.slice(&crease_range, Bias::Left), snapshot);
302
303                let id = self.next_id;
304                self.next_id.0 += 1;
305                self.id_to_range.insert(id, crease_range);
306                new_creases.push(CreaseItem { crease, id }, snapshot);
307                new_ids.push(id);
308            }
309            new_creases.append(cursor.suffix(), snapshot);
310            new_creases
311        };
312        new_ids
313    }
314
315    pub fn remove(
316        &mut self,
317        ids: impl IntoIterator<Item = CreaseId>,
318        snapshot: &MultiBufferSnapshot,
319    ) -> Vec<(CreaseId, Range<Anchor>)> {
320        let mut removals = Vec::new();
321        for id in ids {
322            if let Some(range) = self.id_to_range.remove(&id) {
323                removals.push((id, range.clone()));
324            }
325        }
326        removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
327            AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(a_id))
328        });
329
330        self.snapshot.creases = {
331            let mut new_creases = SumTree::new(snapshot);
332            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
333
334            for (id, range) in &removals {
335                new_creases.append(cursor.slice(range, Bias::Left), snapshot);
336                while let Some(item) = cursor.item() {
337                    cursor.next();
338                    if item.id == *id {
339                        break;
340                    } else {
341                        new_creases.push(item.clone(), snapshot);
342                    }
343                }
344            }
345
346            new_creases.append(cursor.suffix(), snapshot);
347            new_creases
348        };
349
350        removals
351    }
352}
353
354#[derive(Debug, Clone)]
355pub struct ItemSummary {
356    range: Range<Anchor>,
357}
358
359impl Default for ItemSummary {
360    fn default() -> Self {
361        Self {
362            range: Anchor::min()..Anchor::min(),
363        }
364    }
365}
366
367impl sum_tree::Summary for ItemSummary {
368    type Context<'a> = &'a MultiBufferSnapshot;
369
370    fn zero(_cx: Self::Context<'_>) -> Self {
371        Default::default()
372    }
373
374    fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
375        self.range = other.range.clone();
376    }
377}
378
379impl sum_tree::Item for CreaseItem {
380    type Summary = ItemSummary;
381
382    fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
383        ItemSummary {
384            range: self.crease.range().clone(),
385        }
386    }
387}
388
389/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
390impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
391    fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
392        AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
393    }
394}
395
396impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
397    fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
398        self.cmp(&other.range.start, snapshot)
399    }
400}
401
402#[cfg(test)]
403mod test {
404    use super::*;
405    use gpui::{App, div};
406    use multi_buffer::MultiBuffer;
407
408    #[gpui::test]
409    fn test_insert_and_remove_creases(cx: &mut App) {
410        let text = "line1\nline2\nline3\nline4\nline5";
411        let buffer = MultiBuffer::build_simple(text, cx);
412        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
413        let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
414
415        // Insert creases
416        let creases = [
417            Crease::inline(
418                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
419                FoldPlaceholder::test(),
420                |_row, _folded, _toggle, _window, _cx| div(),
421                |_row, _folded, _window, _cx| div(),
422            ),
423            Crease::inline(
424                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
425                FoldPlaceholder::test(),
426                |_row, _folded, _toggle, _window, _cx| div(),
427                |_row, _folded, _window, _cx| div(),
428            ),
429        ];
430        let crease_ids = crease_map.insert(creases, &snapshot);
431        assert_eq!(crease_ids.len(), 2);
432
433        // Verify creases are inserted
434        let crease_snapshot = crease_map.snapshot();
435        assert!(
436            crease_snapshot
437                .query_row(MultiBufferRow(1), &snapshot)
438                .is_some()
439        );
440        assert!(
441            crease_snapshot
442                .query_row(MultiBufferRow(3), &snapshot)
443                .is_some()
444        );
445
446        // Remove creases
447        crease_map.remove(crease_ids, &snapshot);
448
449        // Verify creases are removed
450        let crease_snapshot = crease_map.snapshot();
451        assert!(
452            crease_snapshot
453                .query_row(MultiBufferRow(1), &snapshot)
454                .is_none()
455        );
456        assert!(
457            crease_snapshot
458                .query_row(MultiBufferRow(3), &snapshot)
459                .is_none()
460        );
461    }
462
463    #[gpui::test]
464    fn test_creases_in_range(cx: &mut App) {
465        let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
466        let buffer = MultiBuffer::build_simple(text, cx);
467        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
468        let mut crease_map = CreaseMap::new(&snapshot);
469
470        let creases = [
471            Crease::inline(
472                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
473                FoldPlaceholder::test(),
474                |_row, _folded, _toggle, _window, _cx| div(),
475                |_row, _folded, _window, _cx| div(),
476            ),
477            Crease::inline(
478                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
479                FoldPlaceholder::test(),
480                |_row, _folded, _toggle, _window, _cx| div(),
481                |_row, _folded, _window, _cx| div(),
482            ),
483            Crease::inline(
484                snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
485                FoldPlaceholder::test(),
486                |_row, _folded, _toggle, _window, _cx| div(),
487                |_row, _folded, _window, _cx| div(),
488            ),
489        ];
490        crease_map.insert(creases, &snapshot);
491
492        let crease_snapshot = crease_map.snapshot();
493
494        let range = MultiBufferRow(0)..MultiBufferRow(7);
495        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
496        assert_eq!(creases.len(), 3);
497
498        let range = MultiBufferRow(2)..MultiBufferRow(5);
499        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
500        assert_eq!(creases.len(), 1);
501        assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
502
503        let range = MultiBufferRow(0)..MultiBufferRow(2);
504        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
505        assert_eq!(creases.len(), 1);
506        assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
507
508        let range = MultiBufferRow(6)..MultiBufferRow(7);
509        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
510        assert_eq!(creases.len(), 0);
511    }
512}