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