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