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