crease_map.rs

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