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}