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