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}