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, ops::Range, sync::Arc};
  6use sum_tree::{Bias, SeekTarget, SumTree};
  7use text::Point;
  8use ui::{IconName, SharedString, WindowContext};
  9
 10use crate::FoldPlaceholder;
 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> {
 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> {
 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_start = item.crease.range.start.to_point(snapshot);
 81                let crease_end = item.crease.range.end.to_point(snapshot);
 82                if crease_end.row > range.end.0 {
 83                    continue;
 84                }
 85                if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
 86                    return Some(&item.crease);
 87                }
 88            }
 89            None
 90        })
 91    }
 92
 93    pub fn crease_items_with_offsets(
 94        &self,
 95        snapshot: &MultiBufferSnapshot,
 96    ) -> Vec<(CreaseId, Range<Point>)> {
 97        let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
 98        let mut results = Vec::new();
 99
100        cursor.next(snapshot);
101        while let Some(item) = cursor.item() {
102            let start_point = item.crease.range.start.to_point(snapshot);
103            let end_point = item.crease.range.end.to_point(snapshot);
104            results.push((item.id, start_point..end_point));
105            cursor.next(snapshot);
106        }
107
108        results
109    }
110}
111
112type RenderToggleFn = Arc<
113    dyn Send
114        + Sync
115        + Fn(
116            MultiBufferRow,
117            bool,
118            Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
119            &mut WindowContext,
120        ) -> AnyElement,
121>;
122type RenderTrailerFn =
123    Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
124
125#[derive(Clone)]
126pub struct Crease {
127    pub range: Range<Anchor>,
128    pub placeholder: FoldPlaceholder,
129    pub render_toggle: RenderToggleFn,
130    pub render_trailer: RenderTrailerFn,
131    pub metadata: Option<CreaseMetadata>,
132}
133
134/// Metadata about a [`Crease`], that is used for serialization.
135#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
136pub struct CreaseMetadata {
137    pub icon: IconName,
138    pub label: SharedString,
139}
140
141impl Crease {
142    pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
143        range: Range<Anchor>,
144        placeholder: FoldPlaceholder,
145        render_toggle: RenderToggle,
146        render_trailer: RenderTrailer,
147    ) -> Self
148    where
149        RenderToggle: 'static
150            + Send
151            + Sync
152            + Fn(
153                MultiBufferRow,
154                bool,
155                Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
156                &mut WindowContext,
157            ) -> ToggleElement
158            + 'static,
159        ToggleElement: IntoElement,
160        RenderTrailer: 'static
161            + Send
162            + Sync
163            + Fn(MultiBufferRow, bool, &mut WindowContext) -> TrailerElement
164            + 'static,
165        TrailerElement: IntoElement,
166    {
167        Crease {
168            range,
169            placeholder,
170            render_toggle: Arc::new(move |row, folded, toggle, cx| {
171                render_toggle(row, folded, toggle, cx).into_any_element()
172            }),
173            render_trailer: Arc::new(move |row, folded, cx| {
174                render_trailer(row, folded, cx).into_any_element()
175            }),
176            metadata: None,
177        }
178    }
179
180    pub fn with_metadata(mut self, metadata: CreaseMetadata) -> Self {
181        self.metadata = Some(metadata);
182        self
183    }
184}
185
186impl std::fmt::Debug for Crease {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        f.debug_struct("Crease")
189            .field("range", &self.range)
190            .finish()
191    }
192}
193
194#[derive(Clone, Debug)]
195struct CreaseItem {
196    id: CreaseId,
197    crease: Crease,
198}
199
200impl CreaseMap {
201    pub fn snapshot(&self) -> CreaseSnapshot {
202        self.snapshot.clone()
203    }
204
205    pub fn insert(
206        &mut self,
207        creases: impl IntoIterator<Item = Crease>,
208        snapshot: &MultiBufferSnapshot,
209    ) -> Vec<CreaseId> {
210        let mut new_ids = Vec::new();
211        self.snapshot.creases = {
212            let mut new_creases = SumTree::new(snapshot);
213            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
214            for crease in creases {
215                new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
216
217                let id = self.next_id;
218                self.next_id.0 += 1;
219                self.id_to_range.insert(id, crease.range.clone());
220                new_creases.push(CreaseItem { crease, id }, snapshot);
221                new_ids.push(id);
222            }
223            new_creases.append(cursor.suffix(snapshot), snapshot);
224            new_creases
225        };
226        new_ids
227    }
228
229    pub fn remove(
230        &mut self,
231        ids: impl IntoIterator<Item = CreaseId>,
232        snapshot: &MultiBufferSnapshot,
233    ) {
234        let mut removals = Vec::new();
235        for id in ids {
236            if let Some(range) = self.id_to_range.remove(&id) {
237                removals.push((id, range.clone()));
238            }
239        }
240        removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
241            AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(a_id))
242        });
243
244        self.snapshot.creases = {
245            let mut new_creases = SumTree::new(snapshot);
246            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
247
248            for (id, range) in removals {
249                new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
250                while let Some(item) = cursor.item() {
251                    cursor.next(snapshot);
252                    if item.id == id {
253                        break;
254                    } else {
255                        new_creases.push(item.clone(), snapshot);
256                    }
257                }
258            }
259
260            new_creases.append(cursor.suffix(snapshot), snapshot);
261            new_creases
262        };
263    }
264}
265
266#[derive(Debug, Clone)]
267pub struct ItemSummary {
268    range: Range<Anchor>,
269}
270
271impl Default for ItemSummary {
272    fn default() -> Self {
273        Self {
274            range: Anchor::min()..Anchor::min(),
275        }
276    }
277}
278
279impl sum_tree::Summary for ItemSummary {
280    type Context = MultiBufferSnapshot;
281
282    fn zero(_cx: &Self::Context) -> Self {
283        Default::default()
284    }
285
286    fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
287        self.range = other.range.clone();
288    }
289}
290
291impl sum_tree::Item for CreaseItem {
292    type Summary = ItemSummary;
293
294    fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
295        ItemSummary {
296            range: self.crease.range.clone(),
297        }
298    }
299}
300
301/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
302impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
303    fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
304        AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
305    }
306}
307
308impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
309    fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
310        self.cmp(&other.range.start, snapshot)
311    }
312}
313
314#[cfg(test)]
315mod test {
316    use super::*;
317    use gpui::{div, AppContext};
318    use multi_buffer::MultiBuffer;
319
320    #[gpui::test]
321    fn test_insert_and_remove_creases(cx: &mut AppContext) {
322        let text = "line1\nline2\nline3\nline4\nline5";
323        let buffer = MultiBuffer::build_simple(text, cx);
324        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
325        let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
326
327        // Insert creases
328        let creases = [
329            Crease::new(
330                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
331                FoldPlaceholder::test(),
332                |_row, _folded, _toggle, _cx| div(),
333                |_row, _folded, _cx| div(),
334            ),
335            Crease::new(
336                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
337                FoldPlaceholder::test(),
338                |_row, _folded, _toggle, _cx| div(),
339                |_row, _folded, _cx| div(),
340            ),
341        ];
342        let crease_ids = crease_map.insert(creases, &snapshot);
343        assert_eq!(crease_ids.len(), 2);
344
345        // Verify creases are inserted
346        let crease_snapshot = crease_map.snapshot();
347        assert!(crease_snapshot
348            .query_row(MultiBufferRow(1), &snapshot)
349            .is_some());
350        assert!(crease_snapshot
351            .query_row(MultiBufferRow(3), &snapshot)
352            .is_some());
353
354        // Remove creases
355        crease_map.remove(crease_ids, &snapshot);
356
357        // Verify creases are removed
358        let crease_snapshot = crease_map.snapshot();
359        assert!(crease_snapshot
360            .query_row(MultiBufferRow(1), &snapshot)
361            .is_none());
362        assert!(crease_snapshot
363            .query_row(MultiBufferRow(3), &snapshot)
364            .is_none());
365    }
366
367    #[gpui::test]
368    fn test_creases_in_range(cx: &mut AppContext) {
369        let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
370        let buffer = MultiBuffer::build_simple(text, cx);
371        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
372        let mut crease_map = CreaseMap::new(&snapshot);
373
374        let creases = [
375            Crease::new(
376                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
377                FoldPlaceholder::test(),
378                |_row, _folded, _toggle, _cx| div(),
379                |_row, _folded, _cx| div(),
380            ),
381            Crease::new(
382                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
383                FoldPlaceholder::test(),
384                |_row, _folded, _toggle, _cx| div(),
385                |_row, _folded, _cx| div(),
386            ),
387            Crease::new(
388                snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
389                FoldPlaceholder::test(),
390                |_row, _folded, _toggle, _cx| div(),
391                |_row, _folded, _cx| div(),
392            ),
393        ];
394        crease_map.insert(creases, &snapshot);
395
396        let crease_snapshot = crease_map.snapshot();
397
398        let range = MultiBufferRow(0)..MultiBufferRow(7);
399        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
400        assert_eq!(creases.len(), 3);
401
402        let range = MultiBufferRow(2)..MultiBufferRow(5);
403        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
404        assert_eq!(creases.len(), 1);
405        assert_eq!(creases[0].range.start.to_point(&snapshot).row, 3);
406
407        let range = MultiBufferRow(0)..MultiBufferRow(2);
408        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
409        assert_eq!(creases.len(), 1);
410        assert_eq!(creases[0].range.start.to_point(&snapshot).row, 1);
411
412        let range = MultiBufferRow(6)..MultiBufferRow(7);
413        let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
414        assert_eq!(creases.len(), 0);
415    }
416}