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