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