1use collections::{HashMap, HashSet};
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 let mut ids_to_remove = HashSet::default();
335 for id in ids {
336 if let Some(range) = self.id_to_range.remove(&id) {
337 ids_to_remove.insert(id);
338 removals.push((id, range));
339 }
340 }
341
342 if !ids_to_remove.is_empty() {
343 self.snapshot.creases = {
344 let mut new_creases = SumTree::new(snapshot);
345 for item in self.snapshot.creases.iter() {
346 if !ids_to_remove.contains(&item.id) {
347 new_creases.push(item.clone(), snapshot);
348 }
349 }
350 new_creases
351 };
352 }
353
354 removals
355 }
356}
357
358#[derive(Debug, Clone)]
359pub struct ItemSummary {
360 range: Range<Anchor>,
361}
362
363impl Default for ItemSummary {
364 fn default() -> Self {
365 Self {
366 range: Anchor::min()..Anchor::min(),
367 }
368 }
369}
370
371impl sum_tree::Summary for ItemSummary {
372 type Context<'a> = &'a MultiBufferSnapshot;
373
374 fn zero(_cx: Self::Context<'_>) -> Self {
375 Default::default()
376 }
377
378 fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
379 self.range = other.range.clone();
380 }
381}
382
383impl sum_tree::Item for CreaseItem {
384 type Summary = ItemSummary;
385
386 #[ztracing::instrument(skip_all)]
387 fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
388 ItemSummary {
389 range: self.crease.range().clone(),
390 }
391 }
392}
393
394/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
395impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
396 #[ztracing::instrument(skip_all)]
397 fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
398 AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
399 }
400}
401
402impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
403 #[ztracing::instrument(skip_all)]
404 fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
405 self.cmp(&other.range.start, snapshot)
406 }
407}
408
409#[cfg(test)]
410mod test {
411 use super::*;
412 use gpui::{App, div};
413 use multi_buffer::MultiBuffer;
414
415 #[gpui::test]
416 fn test_insert_and_remove_creases(cx: &mut App) {
417 let text = "line1\nline2\nline3\nline4\nline5";
418 let buffer = MultiBuffer::build_simple(text, cx);
419 let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
420 let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
421
422 // Insert creases
423 let creases = [
424 Crease::inline(
425 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
426 FoldPlaceholder::test(),
427 |_row, _folded, _toggle, _window, _cx| div(),
428 |_row, _folded, _window, _cx| div(),
429 ),
430 Crease::inline(
431 snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
432 FoldPlaceholder::test(),
433 |_row, _folded, _toggle, _window, _cx| div(),
434 |_row, _folded, _window, _cx| div(),
435 ),
436 ];
437 let crease_ids = crease_map.insert(creases, &snapshot);
438 assert_eq!(crease_ids.len(), 2);
439
440 // Verify creases are inserted
441 let crease_snapshot = crease_map.snapshot();
442 assert!(
443 crease_snapshot
444 .query_row(MultiBufferRow(1), &snapshot)
445 .is_some()
446 );
447 assert!(
448 crease_snapshot
449 .query_row(MultiBufferRow(3), &snapshot)
450 .is_some()
451 );
452
453 // Remove creases
454 crease_map.remove(crease_ids, &snapshot);
455
456 // Verify creases are removed
457 let crease_snapshot = crease_map.snapshot();
458 assert!(
459 crease_snapshot
460 .query_row(MultiBufferRow(1), &snapshot)
461 .is_none()
462 );
463 assert!(
464 crease_snapshot
465 .query_row(MultiBufferRow(3), &snapshot)
466 .is_none()
467 );
468 }
469
470 #[gpui::test]
471 #[ztracing::instrument(skip_all)]
472 fn test_creases_in_range(cx: &mut App) {
473 let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
474 let buffer = MultiBuffer::build_simple(text, cx);
475 let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
476 let mut crease_map = CreaseMap::new(&snapshot);
477
478 let creases = [
479 Crease::inline(
480 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
481 FoldPlaceholder::test(),
482 |_row, _folded, _toggle, _window, _cx| div(),
483 |_row, _folded, _window, _cx| div(),
484 ),
485 Crease::inline(
486 snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
487 FoldPlaceholder::test(),
488 |_row, _folded, _toggle, _window, _cx| div(),
489 |_row, _folded, _window, _cx| div(),
490 ),
491 Crease::inline(
492 snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
493 FoldPlaceholder::test(),
494 |_row, _folded, _toggle, _window, _cx| div(),
495 |_row, _folded, _window, _cx| div(),
496 ),
497 ];
498 crease_map.insert(creases, &snapshot);
499
500 let crease_snapshot = crease_map.snapshot();
501
502 let range = MultiBufferRow(0)..MultiBufferRow(7);
503 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
504 assert_eq!(creases.len(), 3);
505
506 let range = MultiBufferRow(2)..MultiBufferRow(5);
507 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
508 assert_eq!(creases.len(), 1);
509 assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
510
511 let range = MultiBufferRow(0)..MultiBufferRow(2);
512 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
513 assert_eq!(creases.len(), 1);
514 assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
515
516 let range = MultiBufferRow(6)..MultiBufferRow(7);
517 let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
518 assert_eq!(creases.len(), 0);
519 }
520}