crease_map.rs

  1use collections::{HashMap, HashSet};
  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        let mut ids_to_remove = HashSet::default();
335        for id in ids {
336            if let Some(range) = self.id_to_range.remove(&id) {
337                ids_to_remove.insert(id);
338                removals.push((id, range));
339            }
340        }
341
342        if !ids_to_remove.is_empty() {
343            self.snapshot.creases = {
344                let mut new_creases = SumTree::new(snapshot);
345                for item in self.snapshot.creases.iter() {
346                    if !ids_to_remove.contains(&item.id) {
347                        new_creases.push(item.clone(), snapshot);
348                    }
349                }
350                new_creases
351            };
352        }
353
354        removals
355    }
356}
357
358#[derive(Debug, Clone)]
359pub struct ItemSummary {
360    range: Range<Anchor>,
361}
362
363impl Default for ItemSummary {
364    fn default() -> Self {
365        Self {
366            range: Anchor::min()..Anchor::min(),
367        }
368    }
369}
370
371impl sum_tree::Summary for ItemSummary {
372    type Context<'a> = &'a MultiBufferSnapshot;
373
374    fn zero(_cx: Self::Context<'_>) -> Self {
375        Default::default()
376    }
377
378    fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
379        self.range = other.range.clone();
380    }
381}
382
383impl sum_tree::Item for CreaseItem {
384    type Summary = ItemSummary;
385
386    #[ztracing::instrument(skip_all)]
387    fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
388        ItemSummary {
389            range: self.crease.range().clone(),
390        }
391    }
392}
393
394/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
395impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
396    #[ztracing::instrument(skip_all)]
397    fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
398        AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
399    }
400}
401
402impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
403    #[ztracing::instrument(skip_all)]
404    fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
405        self.cmp(&other.range.start, snapshot)
406    }
407}
408
409#[cfg(test)]
410mod test {
411    use super::*;
412    use gpui::{App, div};
413    use multi_buffer::MultiBuffer;
414
415    #[gpui::test]
416    fn test_insert_and_remove_creases(cx: &mut App) {
417        let text = "line1\nline2\nline3\nline4\nline5";
418        let buffer = MultiBuffer::build_simple(text, cx);
419        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
420        let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
421
422        // Insert creases
423        let creases = [
424            Crease::inline(
425                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
426                FoldPlaceholder::test(),
427                |_row, _folded, _toggle, _window, _cx| div(),
428                |_row, _folded, _window, _cx| div(),
429            ),
430            Crease::inline(
431                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
432                FoldPlaceholder::test(),
433                |_row, _folded, _toggle, _window, _cx| div(),
434                |_row, _folded, _window, _cx| div(),
435            ),
436        ];
437        let crease_ids = crease_map.insert(creases, &snapshot);
438        assert_eq!(crease_ids.len(), 2);
439
440        // Verify creases are inserted
441        let crease_snapshot = crease_map.snapshot();
442        assert!(
443            crease_snapshot
444                .query_row(MultiBufferRow(1), &snapshot)
445                .is_some()
446        );
447        assert!(
448            crease_snapshot
449                .query_row(MultiBufferRow(3), &snapshot)
450                .is_some()
451        );
452
453        // Remove creases
454        crease_map.remove(crease_ids, &snapshot);
455
456        // Verify creases are removed
457        let crease_snapshot = crease_map.snapshot();
458        assert!(
459            crease_snapshot
460                .query_row(MultiBufferRow(1), &snapshot)
461                .is_none()
462        );
463        assert!(
464            crease_snapshot
465                .query_row(MultiBufferRow(3), &snapshot)
466                .is_none()
467        );
468    }
469
470    #[gpui::test]
471    #[ztracing::instrument(skip_all)]
472    fn test_creases_in_range(cx: &mut App) {
473        let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
474        let buffer = MultiBuffer::build_simple(text, cx);
475        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
476        let mut crease_map = CreaseMap::new(&snapshot);
477
478        let creases = [
479            Crease::inline(
480                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
481                FoldPlaceholder::test(),
482                |_row, _folded, _toggle, _window, _cx| div(),
483                |_row, _folded, _window, _cx| div(),
484            ),
485            Crease::inline(
486                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
487                FoldPlaceholder::test(),
488                |_row, _folded, _toggle, _window, _cx| div(),
489                |_row, _folded, _window, _cx| div(),
490            ),
491            Crease::inline(
492                snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
493                FoldPlaceholder::test(),
494                |_row, _folded, _toggle, _window, _cx| div(),
495                |_row, _folded, _window, _cx| div(),
496            ),
497        ];
498        crease_map.insert(creases, &snapshot);
499
500        let crease_snapshot = crease_map.snapshot();
501
502        let range = MultiBufferRow(0)..MultiBufferRow(7);
503        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
504        assert_eq!(creases.len(), 3);
505
506        let range = MultiBufferRow(2)..MultiBufferRow(5);
507        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
508        assert_eq!(creases.len(), 1);
509        assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
510
511        let range = MultiBufferRow(0)..MultiBufferRow(2);
512        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
513        assert_eq!(creases.len(), 1);
514        assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
515
516        let range = MultiBufferRow(6)..MultiBufferRow(7);
517        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
518        assert_eq!(creases.len(), 0);
519    }
520}