1use collections::HashMap;
2use gpui::{AnyElement, IntoElement};
3use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
4use serde::{Deserialize, Serialize};
5use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
6use sum_tree::{Bias, SeekTarget, SumTree};
7use text::Point;
8use ui::{IconName, SharedString, WindowContext};
9
10use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
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<Anchor>> {
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<Anchor>> {
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_range = item.crease.range();
81 let crease_start = crease_range.start.to_point(snapshot);
82 let crease_end = crease_range.end.to_point(snapshot);
83 if crease_end.row > range.end.0 {
84 continue;
85 }
86 if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
87 return Some(&item.crease);
88 }
89 }
90 None
91 })
92 }
93
94 pub fn crease_items_with_offsets(
95 &self,
96 snapshot: &MultiBufferSnapshot,
97 ) -> Vec<(CreaseId, Range<Point>)> {
98 let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
99 let mut results = Vec::new();
100
101 cursor.next(snapshot);
102 while let Some(item) = cursor.item() {
103 let crease_range = item.crease.range();
104 let start_point = crease_range.start.to_point(snapshot);
105 let end_point = crease_range.end.to_point(snapshot);
106 results.push((item.id, start_point..end_point));
107 cursor.next(snapshot);
108 }
109
110 results
111 }
112}
113
114type RenderToggleFn = Arc<
115 dyn Send
116 + Sync
117 + Fn(
118 MultiBufferRow,
119 bool,
120 Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
121 &mut WindowContext,
122 ) -> AnyElement,
123>;
124type RenderTrailerFn =
125 Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
126
127#[derive(Clone)]
128pub enum Crease<T> {
129 Inline {
130 range: Range<T>,
131 placeholder: FoldPlaceholder,
132 render_toggle: Option<RenderToggleFn>,
133 render_trailer: Option<RenderTrailerFn>,
134 metadata: Option<CreaseMetadata>,
135 },
136 Block {
137 range: Range<T>,
138 block_height: u32,
139 block_style: BlockStyle,
140 render_block: RenderBlock,
141 block_priority: usize,
142 render_toggle: Option<RenderToggleFn>,
143 },
144}
145
146/// Metadata about a [`Crease`], that is used for serialization.
147#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
148pub struct CreaseMetadata {
149 pub icon: IconName,
150 pub label: SharedString,
151}
152
153impl<T> Crease<T> {
154 pub fn simple(range: Range<T>, placeholder: FoldPlaceholder) -> Self {
155 Crease::Inline {
156 range,
157 placeholder,
158 render_toggle: None,
159 render_trailer: None,
160 metadata: None,
161 }
162 }
163
164 pub fn block(range: Range<T>, height: u32, style: BlockStyle, render: RenderBlock) -> Self {
165 Self::Block {
166 range,
167 block_height: height,
168 block_style: style,
169 render_block: render,
170 block_priority: 0,
171 render_toggle: None,
172 }
173 }
174
175 pub fn inline<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
176 range: Range<T>,
177 placeholder: FoldPlaceholder,
178 render_toggle: RenderToggle,
179 render_trailer: RenderTrailer,
180 ) -> Self
181 where
182 RenderToggle: 'static
183 + Send
184 + Sync
185 + Fn(
186 MultiBufferRow,
187 bool,
188 Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
189 &mut WindowContext,
190 ) -> ToggleElement
191 + 'static,
192 ToggleElement: IntoElement,
193 RenderTrailer: 'static
194 + Send
195 + Sync
196 + Fn(MultiBufferRow, bool, &mut WindowContext) -> TrailerElement
197 + 'static,
198 TrailerElement: IntoElement,
199 {
200 Crease::Inline {
201 range,
202 placeholder,
203 render_toggle: Some(Arc::new(move |row, folded, toggle, cx| {
204 render_toggle(row, folded, toggle, cx).into_any_element()
205 })),
206 render_trailer: Some(Arc::new(move |row, folded, cx| {
207 render_trailer(row, folded, cx).into_any_element()
208 })),
209 metadata: None,
210 }
211 }
212
213 pub fn with_metadata(self, metadata: CreaseMetadata) -> Self {
214 match self {
215 Crease::Inline {
216 range,
217 placeholder,
218 render_toggle,
219 render_trailer,
220 ..
221 } => Crease::Inline {
222 range,
223 placeholder,
224 render_toggle,
225 render_trailer,
226 metadata: Some(metadata),
227 },
228 Crease::Block { .. } => self,
229 }
230 }
231
232 pub fn range(&self) -> &Range<T> {
233 match self {
234 Crease::Inline { range, .. } => range,
235 Crease::Block { range, .. } => range,
236 }
237 }
238}
239
240impl<T> std::fmt::Debug for Crease<T>
241where
242 T: Debug,
243{
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Crease::Inline {
247 range, metadata, ..
248 } => f
249 .debug_struct("Crease::Inline")
250 .field("range", range)
251 .field("metadata", metadata)
252 .finish_non_exhaustive(),
253 Crease::Block {
254 range,
255 block_height,
256 ..
257 } => f
258 .debug_struct("Crease::Block")
259 .field("range", range)
260 .field("height", block_height)
261 .finish_non_exhaustive(),
262 }
263 }
264}
265
266#[derive(Clone, Debug)]
267struct CreaseItem {
268 id: CreaseId,
269 crease: Crease<Anchor>,
270}
271
272impl CreaseMap {
273 pub fn snapshot(&self) -> CreaseSnapshot {
274 self.snapshot.clone()
275 }
276
277 pub fn insert(
278 &mut self,
279 creases: impl IntoIterator<Item = Crease<Anchor>>,
280 snapshot: &MultiBufferSnapshot,
281 ) -> Vec<CreaseId> {
282 let mut new_ids = Vec::new();
283 self.snapshot.creases = {
284 let mut new_creases = SumTree::new(snapshot);
285 let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
286 for crease in creases {
287 let crease_range = crease.range().clone();
288 new_creases.append(cursor.slice(&crease_range, Bias::Left, snapshot), snapshot);
289
290 let id = self.next_id;
291 self.next_id.0 += 1;
292 self.id_to_range.insert(id, crease_range);
293 new_creases.push(CreaseItem { crease, id }, snapshot);
294 new_ids.push(id);
295 }
296 new_creases.append(cursor.suffix(snapshot), snapshot);
297 new_creases
298 };
299 new_ids
300 }
301
302 pub fn remove(
303 &mut self,
304 ids: impl IntoIterator<Item = CreaseId>,
305 snapshot: &MultiBufferSnapshot,
306 ) {
307 let mut removals = Vec::new();
308 for id in ids {
309 if let Some(range) = self.id_to_range.remove(&id) {
310 removals.push((id, range.clone()));
311 }
312 }
313 removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
314 AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(a_id))
315 });
316
317 self.snapshot.creases = {
318 let mut new_creases = SumTree::new(snapshot);
319 let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
320
321 for (id, range) in removals {
322 new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
323 while let Some(item) = cursor.item() {
324 cursor.next(snapshot);
325 if item.id == id {
326 break;
327 } else {
328 new_creases.push(item.clone(), snapshot);
329 }
330 }
331 }
332
333 new_creases.append(cursor.suffix(snapshot), snapshot);
334 new_creases
335 };
336 }
337}
338
339#[derive(Debug, Clone)]
340pub struct ItemSummary {
341 range: Range<Anchor>,
342}
343
344impl Default for ItemSummary {
345 fn default() -> Self {
346 Self {
347 range: Anchor::min()..Anchor::min(),
348 }
349 }
350}
351
352impl sum_tree::Summary for ItemSummary {
353 type Context = MultiBufferSnapshot;
354
355 fn zero(_cx: &Self::Context) -> Self {
356 Default::default()
357 }
358
359 fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
360 self.range = other.range.clone();
361 }
362}
363
364impl sum_tree::Item for CreaseItem {
365 type Summary = ItemSummary;
366
367 fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
368 ItemSummary {
369 range: self.crease.range().clone(),
370 }
371 }
372}
373
374/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
375impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
376 fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
377 AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
378 }
379}
380
381impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
382 fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
383 self.cmp(&other.range.start, snapshot)
384 }
385}
386
387#[cfg(test)]
388mod test {
389 use super::*;
390 use gpui::{div, AppContext};
391 use multi_buffer::MultiBuffer;
392
393 #[gpui::test]
394 fn test_insert_and_remove_creases(cx: &mut AppContext) {
395 let text = "line1\nline2\nline3\nline4\nline5";
396 let buffer = MultiBuffer::build_simple(text, cx);
397 let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
398 let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
399
400 // Insert creases
401 let creases = [
402 Crease::inline(
403 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
404 FoldPlaceholder::test(),
405 |_row, _folded, _toggle, _cx| div(),
406 |_row, _folded, _cx| div(),
407 ),
408 Crease::inline(
409 snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
410 FoldPlaceholder::test(),
411 |_row, _folded, _toggle, _cx| div(),
412 |_row, _folded, _cx| div(),
413 ),
414 ];
415 let crease_ids = crease_map.insert(creases, &snapshot);
416 assert_eq!(crease_ids.len(), 2);
417
418 // Verify creases are inserted
419 let crease_snapshot = crease_map.snapshot();
420 assert!(crease_snapshot
421 .query_row(MultiBufferRow(1), &snapshot)
422 .is_some());
423 assert!(crease_snapshot
424 .query_row(MultiBufferRow(3), &snapshot)
425 .is_some());
426
427 // Remove creases
428 crease_map.remove(crease_ids, &snapshot);
429
430 // Verify creases are removed
431 let crease_snapshot = crease_map.snapshot();
432 assert!(crease_snapshot
433 .query_row(MultiBufferRow(1), &snapshot)
434 .is_none());
435 assert!(crease_snapshot
436 .query_row(MultiBufferRow(3), &snapshot)
437 .is_none());
438 }
439
440 #[gpui::test]
441 fn test_creases_in_range(cx: &mut AppContext) {
442 let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
443 let buffer = MultiBuffer::build_simple(text, cx);
444 let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
445 let mut crease_map = CreaseMap::new(&snapshot);
446
447 let creases = [
448 Crease::inline(
449 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
450 FoldPlaceholder::test(),
451 |_row, _folded, _toggle, _cx| div(),
452 |_row, _folded, _cx| div(),
453 ),
454 Crease::inline(
455 snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
456 FoldPlaceholder::test(),
457 |_row, _folded, _toggle, _cx| div(),
458 |_row, _folded, _cx| div(),
459 ),
460 Crease::inline(
461 snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
462 FoldPlaceholder::test(),
463 |_row, _folded, _toggle, _cx| div(),
464 |_row, _folded, _cx| div(),
465 ),
466 ];
467 crease_map.insert(creases, &snapshot);
468
469 let crease_snapshot = crease_map.snapshot();
470
471 let range = MultiBufferRow(0)..MultiBufferRow(7);
472 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
473 assert_eq!(creases.len(), 3);
474
475 let range = MultiBufferRow(2)..MultiBufferRow(5);
476 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
477 assert_eq!(creases.len(), 1);
478 assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
479
480 let range = MultiBufferRow(0)..MultiBufferRow(2);
481 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
482 assert_eq!(creases.len(), 1);
483 assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
484
485 let range = MultiBufferRow(6)..MultiBufferRow(7);
486 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
487 assert_eq!(creases.len(), 0);
488 }
489}