crease_map.rs

  1use collections::HashMap;
  2use gpui::{AnyElement, IntoElement};
  3use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
  4use std::{cmp::Ordering, ops::Range, sync::Arc};
  5use sum_tree::{Bias, SeekTarget, SumTree};
  6use text::Point;
  7use ui::WindowContext;
  8
  9use crate::FoldPlaceholder;
 10
 11#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
 12pub struct CreaseId(usize);
 13
 14#[derive(Default)]
 15pub struct CreaseMap {
 16    snapshot: CreaseSnapshot,
 17    next_id: CreaseId,
 18    id_to_range: HashMap<CreaseId, Range<Anchor>>,
 19}
 20
 21#[derive(Clone, Default)]
 22pub struct CreaseSnapshot {
 23    creases: SumTree<CreaseItem>,
 24}
 25
 26impl CreaseSnapshot {
 27    /// Returns the first Crease starting on the specified buffer row.
 28    pub fn query_row<'a>(
 29        &'a self,
 30        row: MultiBufferRow,
 31        snapshot: &'a MultiBufferSnapshot,
 32    ) -> Option<&'a Crease> {
 33        let start = snapshot.anchor_before(Point::new(row.0, 0));
 34        let mut cursor = self.creases.cursor::<ItemSummary>();
 35        cursor.seek(&start, Bias::Left, snapshot);
 36        while let Some(item) = cursor.item() {
 37            match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
 38                Ordering::Less => cursor.next(snapshot),
 39                Ordering::Equal => {
 40                    if item.crease.range.start.is_valid(snapshot) {
 41                        return Some(&item.crease);
 42                    } else {
 43                        cursor.next(snapshot);
 44                    }
 45                }
 46                Ordering::Greater => break,
 47            }
 48        }
 49        return None;
 50    }
 51
 52    pub fn crease_items_with_offsets(
 53        &self,
 54        snapshot: &MultiBufferSnapshot,
 55    ) -> Vec<(CreaseId, Range<Point>)> {
 56        let mut cursor = self.creases.cursor::<ItemSummary>();
 57        let mut results = Vec::new();
 58
 59        cursor.next(snapshot);
 60        while let Some(item) = cursor.item() {
 61            let start_point = item.crease.range.start.to_point(snapshot);
 62            let end_point = item.crease.range.end.to_point(snapshot);
 63            results.push((item.id, start_point..end_point));
 64            cursor.next(snapshot);
 65        }
 66
 67        results
 68    }
 69}
 70
 71type RenderToggleFn = Arc<
 72    dyn Send
 73        + Sync
 74        + Fn(
 75            MultiBufferRow,
 76            bool,
 77            Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
 78            &mut WindowContext,
 79        ) -> AnyElement,
 80>;
 81type RenderTrailerFn =
 82    Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
 83
 84#[derive(Clone)]
 85pub struct Crease {
 86    pub range: Range<Anchor>,
 87    pub placeholder: FoldPlaceholder,
 88    pub render_toggle: RenderToggleFn,
 89    pub render_trailer: RenderTrailerFn,
 90}
 91
 92impl Crease {
 93    pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
 94        range: Range<Anchor>,
 95        placeholder: FoldPlaceholder,
 96        render_toggle: RenderToggle,
 97        render_trailer: RenderTrailer,
 98    ) -> Self
 99    where
100        RenderToggle: 'static
101            + Send
102            + Sync
103            + Fn(
104                MultiBufferRow,
105                bool,
106                Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
107                &mut WindowContext,
108            ) -> ToggleElement
109            + 'static,
110        ToggleElement: IntoElement,
111        RenderTrailer: 'static
112            + Send
113            + Sync
114            + Fn(MultiBufferRow, bool, &mut WindowContext) -> TrailerElement
115            + 'static,
116        TrailerElement: IntoElement,
117    {
118        Crease {
119            range,
120            placeholder,
121            render_toggle: Arc::new(move |row, folded, toggle, cx| {
122                render_toggle(row, folded, toggle, cx).into_any_element()
123            }),
124            render_trailer: Arc::new(move |row, folded, cx| {
125                render_trailer(row, folded, cx).into_any_element()
126            }),
127        }
128    }
129}
130
131impl std::fmt::Debug for Crease {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.debug_struct("Crease")
134            .field("range", &self.range)
135            .finish()
136    }
137}
138
139#[derive(Clone, Debug)]
140struct CreaseItem {
141    id: CreaseId,
142    crease: Crease,
143}
144
145impl CreaseMap {
146    pub fn snapshot(&self) -> CreaseSnapshot {
147        self.snapshot.clone()
148    }
149
150    pub fn insert(
151        &mut self,
152        creases: impl IntoIterator<Item = Crease>,
153        snapshot: &MultiBufferSnapshot,
154    ) -> Vec<CreaseId> {
155        let mut new_ids = Vec::new();
156        self.snapshot.creases = {
157            let mut new_creases = SumTree::new();
158            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
159            for crease in creases {
160                new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
161
162                let id = self.next_id;
163                self.next_id.0 += 1;
164                self.id_to_range.insert(id, crease.range.clone());
165                new_creases.push(CreaseItem { crease, id }, snapshot);
166                new_ids.push(id);
167            }
168            new_creases.append(cursor.suffix(snapshot), snapshot);
169            new_creases
170        };
171        new_ids
172    }
173
174    pub fn remove(
175        &mut self,
176        ids: impl IntoIterator<Item = CreaseId>,
177        snapshot: &MultiBufferSnapshot,
178    ) {
179        let mut removals = Vec::new();
180        for id in ids {
181            if let Some(range) = self.id_to_range.remove(&id) {
182                removals.push((id, range.clone()));
183            }
184        }
185        removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
186            AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
187        });
188
189        self.snapshot.creases = {
190            let mut new_creases = SumTree::new();
191            let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
192
193            for (id, range) in removals {
194                new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
195                while let Some(item) = cursor.item() {
196                    cursor.next(snapshot);
197                    if item.id == id {
198                        break;
199                    } else {
200                        new_creases.push(item.clone(), snapshot);
201                    }
202                }
203            }
204
205            new_creases.append(cursor.suffix(snapshot), snapshot);
206            new_creases
207        };
208    }
209}
210
211#[derive(Debug, Clone)]
212pub struct ItemSummary {
213    range: Range<Anchor>,
214}
215
216impl Default for ItemSummary {
217    fn default() -> Self {
218        Self {
219            range: Anchor::min()..Anchor::min(),
220        }
221    }
222}
223
224impl sum_tree::Summary for ItemSummary {
225    type Context = MultiBufferSnapshot;
226
227    fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
228        self.range = other.range.clone();
229    }
230}
231
232impl sum_tree::Item for CreaseItem {
233    type Summary = ItemSummary;
234
235    fn summary(&self) -> Self::Summary {
236        ItemSummary {
237            range: self.crease.range.clone(),
238        }
239    }
240}
241
242/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
243impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
244    fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
245        AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
246    }
247}
248
249impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
250    fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
251        self.cmp(&other.range.start, snapshot)
252    }
253}
254
255#[cfg(test)]
256mod test {
257    use super::*;
258    use gpui::{div, AppContext};
259    use multi_buffer::MultiBuffer;
260
261    #[gpui::test]
262    fn test_insert_and_remove_creases(cx: &mut AppContext) {
263        let text = "line1\nline2\nline3\nline4\nline5";
264        let buffer = MultiBuffer::build_simple(text, cx);
265        let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
266        let mut crease_map = CreaseMap::default();
267
268        // Insert creases
269        let creases = [
270            Crease::new(
271                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
272                FoldPlaceholder::test(),
273                |_row, _folded, _toggle, _cx| div(),
274                |_row, _folded, _cx| div(),
275            ),
276            Crease::new(
277                snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
278                FoldPlaceholder::test(),
279                |_row, _folded, _toggle, _cx| div(),
280                |_row, _folded, _cx| div(),
281            ),
282        ];
283        let crease_ids = crease_map.insert(creases, &snapshot);
284        assert_eq!(crease_ids.len(), 2);
285
286        // Verify creases are inserted
287        let crease_snapshot = crease_map.snapshot();
288        assert!(crease_snapshot
289            .query_row(MultiBufferRow(1), &snapshot)
290            .is_some());
291        assert!(crease_snapshot
292            .query_row(MultiBufferRow(3), &snapshot)
293            .is_some());
294
295        // Remove creases
296        crease_map.remove(crease_ids, &snapshot);
297
298        // Verify creases are removed
299        let crease_snapshot = crease_map.snapshot();
300        assert!(crease_snapshot
301            .query_row(MultiBufferRow(1), &snapshot)
302            .is_none());
303        assert!(crease_snapshot
304            .query_row(MultiBufferRow(3), &snapshot)
305            .is_none());
306    }
307}