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