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