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