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