Skip to repository content1096 lines · 38.6 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:32:56.694Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
fold.rs
1use super::*;
2
3impl GutterDimensions {
4 /// The width of the space reserved for the fold indicators,
5 /// use alongside 'justify_end' and `gutter_width` to
6 /// right align content with the line numbers
7 pub fn fold_area_width(&self) -> Pixels {
8 self.margin + self.right_padding
9 }
10}
11
12impl EditorSnapshot {
13 pub fn render_crease_toggle(
14 &self,
15 buffer_row: MultiBufferRow,
16 row_contains_cursor: bool,
17 editor: Entity<Editor>,
18 window: &mut Window,
19 cx: &mut App,
20 ) -> Option<AnyElement> {
21 let folded = self.is_line_folded(buffer_row);
22 let mut is_foldable = false;
23
24 if let Some(crease) = self
25 .crease_snapshot
26 .query_row(buffer_row, self.buffer_snapshot())
27 {
28 is_foldable = true;
29 match crease {
30 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
31 if let Some(render_toggle) = render_toggle {
32 let toggle_callback =
33 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
34 if folded {
35 editor.update(cx, |editor, cx| {
36 editor.fold_at(buffer_row, window, cx)
37 });
38 } else {
39 editor.update(cx, |editor, cx| {
40 editor.unfold_at(buffer_row, window, cx)
41 });
42 }
43 });
44 return Some((render_toggle)(
45 buffer_row,
46 folded,
47 toggle_callback,
48 window,
49 cx,
50 ));
51 }
52 }
53 }
54 }
55
56 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
57
58 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
59 Some(
60 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
61 .toggle_state(folded)
62 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
63 if folded {
64 this.unfold_at(buffer_row, window, cx);
65 } else {
66 this.fold_at(buffer_row, window, cx);
67 }
68 }))
69 .into_any_element(),
70 )
71 } else {
72 None
73 }
74 }
75
76 pub fn render_crease_trailer(
77 &self,
78 buffer_row: MultiBufferRow,
79 window: &mut Window,
80 cx: &mut App,
81 ) -> Option<AnyElement> {
82 let folded = self.is_line_folded(buffer_row);
83 if let Crease::Inline { render_trailer, .. } = self
84 .crease_snapshot
85 .query_row(buffer_row, self.buffer_snapshot())?
86 {
87 let render_trailer = render_trailer.as_ref()?;
88 Some(render_trailer(buffer_row, folded, window, cx))
89 } else {
90 None
91 }
92 }
93}
94
95impl Editor {
96 pub fn toggle_fold(
97 &mut self,
98 _: &actions::ToggleFold,
99 window: &mut Window,
100 cx: &mut Context<Self>,
101 ) {
102 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
104 let selection = self.selections.newest::<Point>(&display_map);
105
106 let range = if selection.is_empty() {
107 let point = selection.head().to_display_point(&display_map);
108 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
109 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
110 .to_point(&display_map);
111 start..end
112 } else {
113 selection.range()
114 };
115 if display_map.folds_in_range(range).next().is_some() {
116 self.unfold_lines(&Default::default(), window, cx)
117 } else {
118 self.fold(&Default::default(), window, cx)
119 }
120 } else {
121 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
122 let buffer_ids: HashSet<_> = self
123 .selections
124 .disjoint_anchor_ranges()
125 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
126 .collect();
127
128 let should_unfold = buffer_ids
129 .iter()
130 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
131
132 for buffer_id in buffer_ids {
133 if should_unfold {
134 self.unfold_buffer(buffer_id, cx);
135 } else {
136 self.fold_buffer(buffer_id, cx);
137 }
138 }
139 }
140 }
141
142 pub fn toggle_fold_recursive(
143 &mut self,
144 _: &actions::ToggleFoldRecursive,
145 window: &mut Window,
146 cx: &mut Context<Self>,
147 ) {
148 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
149
150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
151 let range = if selection.is_empty() {
152 let point = selection.head().to_display_point(&display_map);
153 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
154 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
155 .to_point(&display_map);
156 start..end
157 } else {
158 selection.range()
159 };
160 if display_map.folds_in_range(range).next().is_some() {
161 self.unfold_recursive(&Default::default(), window, cx)
162 } else {
163 self.fold_recursive(&Default::default(), window, cx)
164 }
165 }
166
167 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
168 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
169 let mut to_fold = Vec::new();
170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
171 let selections = self.selections.all_adjusted(&display_map);
172
173 for selection in selections {
174 let range = selection.range().sorted();
175 let buffer_start_row = range.start.row;
176
177 if range.start.row != range.end.row {
178 let mut found = false;
179 let mut row = range.start.row;
180 while row <= range.end.row {
181 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
182 {
183 found = true;
184 row = crease.range().end.row + 1;
185 to_fold.push(crease);
186 } else {
187 row += 1
188 }
189 }
190 if found {
191 continue;
192 }
193 }
194
195 for row in (0..=range.start.row).rev() {
196 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
197 && crease.range().end.row >= buffer_start_row
198 {
199 to_fold.push(crease);
200 if row <= range.start.row {
201 break;
202 }
203 }
204 }
205 }
206
207 self.fold_creases(to_fold, true, window, cx);
208 } else {
209 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
210 let buffer_ids = self
211 .selections
212 .disjoint_anchor_ranges()
213 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
214 .collect::<HashSet<_>>();
215 for buffer_id in buffer_ids {
216 self.fold_buffer(buffer_id, cx);
217 }
218 }
219 }
220
221 pub fn toggle_fold_all(
222 &mut self,
223 _: &actions::ToggleFoldAll,
224 window: &mut Window,
225 cx: &mut Context<Self>,
226 ) {
227 let has_folds = if self.buffer.read(cx).is_singleton() {
228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
229 let has_folds = display_map
230 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
231 .next()
232 .is_some();
233 has_folds
234 } else {
235 let snapshot = self.buffer.read(cx).snapshot(cx);
236 let has_folds = snapshot
237 .all_buffer_ids()
238 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
239 has_folds
240 };
241
242 if has_folds {
243 self.unfold_all(&actions::UnfoldAll, window, cx);
244 } else {
245 self.fold_all(&actions::FoldAll, window, cx);
246 }
247 }
248
249 pub fn fold_at_level_1(
250 &mut self,
251 _: &actions::FoldAtLevel1,
252 window: &mut Window,
253 cx: &mut Context<Self>,
254 ) {
255 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
256 }
257
258 pub fn fold_at_level_2(
259 &mut self,
260 _: &actions::FoldAtLevel2,
261 window: &mut Window,
262 cx: &mut Context<Self>,
263 ) {
264 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
265 }
266
267 pub fn fold_at_level_3(
268 &mut self,
269 _: &actions::FoldAtLevel3,
270 window: &mut Window,
271 cx: &mut Context<Self>,
272 ) {
273 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
274 }
275
276 pub fn fold_at_level_4(
277 &mut self,
278 _: &actions::FoldAtLevel4,
279 window: &mut Window,
280 cx: &mut Context<Self>,
281 ) {
282 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
283 }
284
285 pub fn fold_at_level_5(
286 &mut self,
287 _: &actions::FoldAtLevel5,
288 window: &mut Window,
289 cx: &mut Context<Self>,
290 ) {
291 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
292 }
293
294 pub fn fold_at_level_6(
295 &mut self,
296 _: &actions::FoldAtLevel6,
297 window: &mut Window,
298 cx: &mut Context<Self>,
299 ) {
300 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
301 }
302
303 pub fn fold_at_level_7(
304 &mut self,
305 _: &actions::FoldAtLevel7,
306 window: &mut Window,
307 cx: &mut Context<Self>,
308 ) {
309 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
310 }
311
312 pub fn fold_at_level_8(
313 &mut self,
314 _: &actions::FoldAtLevel8,
315 window: &mut Window,
316 cx: &mut Context<Self>,
317 ) {
318 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
319 }
320
321 pub fn fold_at_level_9(
322 &mut self,
323 _: &actions::FoldAtLevel9,
324 window: &mut Window,
325 cx: &mut Context<Self>,
326 ) {
327 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
328 }
329
330 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
331 if self.buffer.read(cx).is_singleton() {
332 let mut fold_ranges = Vec::new();
333 let snapshot = self.buffer.read(cx).snapshot(cx);
334
335 for row in 0..snapshot.max_row().0 {
336 if let Some(foldable_range) = self
337 .snapshot(window, cx)
338 .crease_for_buffer_row(MultiBufferRow(row))
339 {
340 fold_ranges.push(foldable_range);
341 }
342 }
343
344 self.fold_creases(fold_ranges, true, window, cx);
345 } else {
346 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
347 editor
348 .update_in(cx, |editor, _, cx| {
349 let snapshot = editor.buffer.read(cx).snapshot(cx);
350 for buffer_id in snapshot.all_buffer_ids() {
351 editor.fold_buffer(buffer_id, cx);
352 }
353 })
354 .ok();
355 });
356 }
357 }
358
359 pub fn fold_function_bodies(
360 &mut self,
361 _: &actions::FoldFunctionBodies,
362 window: &mut Window,
363 cx: &mut Context<Self>,
364 ) {
365 let snapshot = self.buffer.read(cx).snapshot(cx);
366
367 let ranges = snapshot
368 .text_object_ranges(
369 MultiBufferOffset(0)..snapshot.len(),
370 TreeSitterOptions::default(),
371 )
372 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
373 .collect::<Vec<_>>();
374
375 let creases = ranges
376 .into_iter()
377 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
378 .collect();
379
380 self.fold_creases(creases, true, window, cx);
381 }
382
383 pub fn fold_recursive(
384 &mut self,
385 _: &actions::FoldRecursive,
386 window: &mut Window,
387 cx: &mut Context<Self>,
388 ) {
389 let mut to_fold = Vec::new();
390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
391 let selections = self.selections.all_adjusted(&display_map);
392
393 for selection in selections {
394 let range = selection.range().sorted();
395 let buffer_start_row = range.start.row;
396
397 if range.start.row != range.end.row {
398 let mut found = false;
399 for row in range.start.row..=range.end.row {
400 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
401 found = true;
402 to_fold.push(crease);
403 }
404 }
405 if found {
406 continue;
407 }
408 }
409
410 for row in (0..=range.start.row).rev() {
411 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
412 if crease.range().end.row >= buffer_start_row {
413 to_fold.push(crease);
414 } else {
415 break;
416 }
417 }
418 }
419 }
420
421 self.fold_creases(to_fold, true, window, cx);
422 }
423
424 pub fn fold_at(
425 &mut self,
426 buffer_row: MultiBufferRow,
427 window: &mut Window,
428 cx: &mut Context<Self>,
429 ) {
430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
431
432 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
433 let autoscroll = self
434 .selections
435 .all::<Point>(&display_map)
436 .iter()
437 .any(|selection| crease.range().overlaps(&selection.range()));
438
439 self.fold_creases(vec![crease], autoscroll, window, cx);
440 }
441 }
442
443 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
444 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
445 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
446 let buffer = display_map.buffer_snapshot();
447 let selections = self.selections.all::<Point>(&display_map);
448 let ranges = selections
449 .iter()
450 .map(|s| {
451 let range = s.display_range(&display_map).sorted();
452 let mut start = range.start.to_point(&display_map);
453 let mut end = range.end.to_point(&display_map);
454 start.column = 0;
455 end.column = buffer.line_len(MultiBufferRow(end.row));
456 start..end
457 })
458 .collect::<Vec<_>>();
459
460 self.unfold_ranges(&ranges, true, true, cx);
461 } else {
462 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
463 let buffer_ids = self
464 .selections
465 .disjoint_anchor_ranges()
466 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
467 .collect::<HashSet<_>>();
468 for buffer_id in buffer_ids {
469 self.unfold_buffer(buffer_id, cx);
470 }
471 }
472 }
473
474 pub fn unfold_recursive(
475 &mut self,
476 _: &UnfoldRecursive,
477 _window: &mut Window,
478 cx: &mut Context<Self>,
479 ) {
480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
481 let selections = self.selections.all::<Point>(&display_map);
482 let ranges = selections
483 .iter()
484 .map(|s| {
485 let mut range = s.display_range(&display_map).sorted();
486 *range.start.column_mut() = 0;
487 *range.end.column_mut() = display_map.line_len(range.end.row());
488 let start = range.start.to_point(&display_map);
489 let end = range.end.to_point(&display_map);
490 start..end
491 })
492 .collect::<Vec<_>>();
493
494 self.unfold_ranges(&ranges, true, true, cx);
495 }
496
497 pub fn unfold_at(
498 &mut self,
499 buffer_row: MultiBufferRow,
500 _window: &mut Window,
501 cx: &mut Context<Self>,
502 ) {
503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
504
505 let intersection_range = Point::new(buffer_row.0, 0)
506 ..Point::new(
507 buffer_row.0,
508 display_map.buffer_snapshot().line_len(buffer_row),
509 );
510
511 let autoscroll = self
512 .selections
513 .all::<Point>(&display_map)
514 .iter()
515 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
516
517 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
518 }
519
520 pub fn unfold_all(
521 &mut self,
522 _: &actions::UnfoldAll,
523 _window: &mut Window,
524 cx: &mut Context<Self>,
525 ) {
526 if self.buffer.read(cx).is_singleton() {
527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
528 self.unfold_ranges(
529 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
530 true,
531 true,
532 cx,
533 );
534 } else {
535 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
536 editor
537 .update(cx, |editor, cx| {
538 let snapshot = editor.buffer.read(cx).snapshot(cx);
539 for buffer_id in snapshot.all_buffer_ids() {
540 editor.unfold_buffer(buffer_id, cx);
541 }
542 })
543 .ok();
544 });
545 }
546 }
547
548 pub fn fold_selected_ranges(
549 &mut self,
550 _: &FoldSelectedRanges,
551 window: &mut Window,
552 cx: &mut Context<Self>,
553 ) {
554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
555 let selections = self.selections.all_adjusted(&display_map);
556 let ranges = selections
557 .into_iter()
558 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
559 .collect::<Vec<_>>();
560 self.fold_creases(ranges, true, window, cx);
561 }
562
563 pub fn fold_ranges<T: ToOffset + Clone>(
564 &mut self,
565 ranges: Vec<Range<T>>,
566 auto_scroll: bool,
567 window: &mut Window,
568 cx: &mut Context<Self>,
569 ) {
570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
571 let ranges = ranges
572 .into_iter()
573 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
574 .collect::<Vec<_>>();
575 self.fold_creases(ranges, auto_scroll, window, cx);
576 }
577
578 pub fn fold_creases<T: ToOffset + Clone>(
579 &mut self,
580 creases: Vec<Crease<T>>,
581 auto_scroll: bool,
582 window: &mut Window,
583 cx: &mut Context<Self>,
584 ) {
585 if creases.is_empty() {
586 return;
587 }
588
589 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
590
591 if auto_scroll {
592 self.request_autoscroll(Autoscroll::fit(), cx);
593 }
594
595 cx.notify();
596
597 self.scrollbar_marker_state.dirty = true;
598 self.update_data_on_scroll(false, window, cx);
599 self.folds_did_change(cx);
600 }
601
602 /// Removes any folds whose ranges intersect any of the given ranges.
603 pub fn unfold_ranges<T: ToOffset + Clone>(
604 &mut self,
605 ranges: &[Range<T>],
606 inclusive: bool,
607 auto_scroll: bool,
608 cx: &mut Context<Self>,
609 ) {
610 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
611 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
612 });
613 self.folds_did_change(cx);
614 }
615
616 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
617 self.fold_buffers([buffer_id], cx);
618 }
619
620 pub fn fold_buffers(
621 &mut self,
622 buffer_ids: impl IntoIterator<Item = BufferId>,
623 cx: &mut Context<Self>,
624 ) {
625 if self.buffer().read(cx).is_singleton() {
626 return;
627 }
628
629 let ids_to_fold: Vec<BufferId> = buffer_ids
630 .into_iter()
631 .filter(|id| !self.is_buffer_folded(*id, cx))
632 .collect();
633
634 if ids_to_fold.is_empty() {
635 return;
636 }
637
638 self.display_map.update(cx, |display_map, cx| {
639 display_map.fold_buffers(ids_to_fold.clone(), cx)
640 });
641
642 let snapshot = self.display_snapshot(cx);
643 self.selections.change_with(&snapshot, |selections| {
644 for buffer_id in ids_to_fold.iter().copied() {
645 selections.remove_selections_from_buffer(buffer_id);
646 }
647 });
648
649 cx.emit(EditorEvent::BufferFoldToggled {
650 ids: ids_to_fold,
651 folded: true,
652 });
653 cx.notify();
654 }
655
656 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
657 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
658 return;
659 }
660 self.display_map.update(cx, |display_map, cx| {
661 display_map.unfold_buffers([buffer_id], cx);
662 });
663 cx.emit(EditorEvent::BufferFoldToggled {
664 ids: vec![buffer_id],
665 folded: false,
666 });
667 cx.notify();
668 }
669
670 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
671 self.display_map.read(cx).is_buffer_folded(buffer)
672 }
673
674 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
675 if self.buffer().read(cx).is_singleton() {
676 return false;
677 }
678 !self.folded_buffers(cx).is_empty()
679 }
680
681 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
682 self.display_map.read(cx).folded_buffers()
683 }
684
685 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
686 self.display_map.update(cx, |display_map, cx| {
687 display_map.disable_header_for_buffer(buffer_id, cx);
688 });
689 cx.notify();
690 }
691
692 /// Removes any folds with the given ranges.
693 pub fn remove_folds_with_type<T: ToOffset + Clone>(
694 &mut self,
695 ranges: &[Range<T>],
696 type_id: TypeId,
697 auto_scroll: bool,
698 cx: &mut Context<Self>,
699 ) {
700 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
701 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
702 });
703 self.folds_did_change(cx);
704 }
705
706 pub fn update_renderer_widths(
707 &mut self,
708 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
709 cx: &mut Context<Self>,
710 ) -> bool {
711 self.display_map
712 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
713 }
714
715 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
716 self.display_map.read(cx).fold_placeholder.clone()
717 }
718
719 pub fn insert_creases(
720 &mut self,
721 creases: impl IntoIterator<Item = Crease<Anchor>>,
722 cx: &mut Context<Self>,
723 ) -> Vec<CreaseId> {
724 self.display_map
725 .update(cx, |map, cx| map.insert_creases(creases, cx))
726 }
727
728 pub fn remove_creases(
729 &mut self,
730 ids: impl IntoIterator<Item = CreaseId>,
731 cx: &mut Context<Self>,
732 ) -> Vec<(CreaseId, Range<Anchor>)> {
733 self.display_map
734 .update(cx, |map, cx| map.remove_creases(ids, cx))
735 }
736
737 pub(super) fn fold_at_level(
738 &mut self,
739 fold_at: &FoldAtLevel,
740 window: &mut Window,
741 cx: &mut Context<Self>,
742 ) {
743 if !self.buffer.read(cx).is_singleton() {
744 return;
745 }
746
747 let fold_at_level = fold_at.0;
748 let snapshot = self.buffer.read(cx).snapshot(cx);
749 let mut to_fold = Vec::new();
750 let mut stack = vec![(0, snapshot.max_row().0, 1)];
751
752 let row_ranges_to_keep: Vec<Range<u32>> = self
753 .selections
754 .all::<Point>(&self.display_snapshot(cx))
755 .into_iter()
756 .map(|sel| sel.start.row..sel.end.row)
757 .collect();
758
759 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
760 while start_row < end_row {
761 match self
762 .snapshot(window, cx)
763 .crease_for_buffer_row(MultiBufferRow(start_row))
764 {
765 Some(crease) => {
766 let nested_start_row = crease.range().start.row + 1;
767 let nested_end_row = crease.range().end.row;
768
769 if current_level < fold_at_level {
770 stack.push((nested_start_row, nested_end_row, current_level + 1));
771 } else if current_level == fold_at_level {
772 // Fold iff there is no selection completely contained within the fold region
773 if !row_ranges_to_keep.iter().any(|selection| {
774 selection.end >= nested_start_row
775 && selection.start <= nested_end_row
776 }) {
777 to_fold.push(crease);
778 }
779 }
780
781 start_row = nested_end_row + 1;
782 }
783 None => start_row += 1,
784 }
785 }
786 }
787
788 self.fold_creases(to_fold, true, window, cx);
789 }
790
791 pub(super) fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
792 if self.buffer().read(cx).is_singleton() {
793 return;
794 }
795 let snapshot = self.buffer.read(cx).snapshot(cx);
796 let buffer_ids: HashSet<BufferId> = self
797 .selections
798 .disjoint_anchor_ranges()
799 .flat_map(|range| snapshot.buffer_ids_for_range(range))
800 .collect();
801 for buffer_id in buffer_ids {
802 self.unfold_buffer(buffer_id, cx);
803 }
804 }
805
806 pub(super) fn folds_did_change(&mut self, cx: &mut Context<Self>) {
807 use text::ToOffset as _;
808
809 if self.mode.is_minimap()
810 || WorkspaceSettings::get(None, cx).restore_on_startup
811 == RestoreOnStartupBehavior::EmptyTab
812 {
813 return;
814 }
815
816 let display_snapshot = self
817 .display_map
818 .update(cx, |display_map, cx| display_map.snapshot(cx));
819 let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
820 return;
821 };
822 let inmemory_folds = display_snapshot
823 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
824 .map(|fold| {
825 let start = fold.range.start.text_anchor_in(buffer_snapshot);
826 let end = fold.range.end.text_anchor_in(buffer_snapshot);
827 (start..end).to_point(buffer_snapshot)
828 })
829 .collect();
830 self.update_restoration_data(cx, |data| {
831 data.folds = inmemory_folds;
832 });
833
834 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
835 return;
836 };
837
838 // Get file path for path-based fold storage (survives tab close)
839 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
840 project::File::from_dyn(buffer.read(cx).file())
841 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
842 }) else {
843 return;
844 };
845
846 let background_executor = cx.background_executor().clone();
847 const FINGERPRINT_LEN: usize = 32;
848 let db_folds = display_snapshot
849 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
850 .map(|fold| {
851 let start = fold
852 .range
853 .start
854 .text_anchor_in(buffer_snapshot)
855 .to_offset(buffer_snapshot);
856 let end = fold
857 .range
858 .end
859 .text_anchor_in(buffer_snapshot)
860 .to_offset(buffer_snapshot);
861
862 // Extract fingerprints - content at fold boundaries for validation on restore
863 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
864 // content that might change independently.
865 // start_fp: first min(32, fold_len) bytes of fold content
866 // end_fp: last min(32, fold_len) bytes of fold content
867 // Clip to character boundaries to handle multibyte UTF-8 characters.
868 let fold_len = end - start;
869 let start_fp_end = buffer_snapshot
870 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
871 let start_fp: String = buffer_snapshot
872 .text_for_range(start..start_fp_end)
873 .collect();
874 let end_fp_start = buffer_snapshot
875 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
876 let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
877
878 (start, end, start_fp, end_fp)
879 })
880 .collect::<Vec<_>>();
881 let db = EditorDb::global(cx);
882 self.serialize_folds = cx.background_spawn(async move {
883 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
884 if db_folds.is_empty() {
885 // No folds - delete any persisted folds for this file
886 db.delete_file_folds(workspace_id, file_path)
887 .await
888 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
889 .log_err();
890 } else {
891 db.save_file_folds(workspace_id, file_path, db_folds)
892 .await
893 .with_context(|| {
894 format!("persisting file folds for workspace {workspace_id:?}")
895 })
896 .log_err();
897 }
898 });
899 }
900
901 pub(super) fn refresh_single_line_folds(
902 &mut self,
903 window: &mut Window,
904 cx: &mut Context<Editor>,
905 ) {
906 struct NewlineFold;
907 let type_id = std::any::TypeId::of::<NewlineFold>();
908 if !self.mode.is_single_line() {
909 return;
910 }
911 let snapshot = self.snapshot(window, cx);
912 if snapshot.buffer_snapshot().max_point().row == 0 {
913 return;
914 }
915 let task = cx.background_spawn(async move {
916 let new_newlines = snapshot
917 .buffer_chars_at(MultiBufferOffset(0))
918 .filter_map(|(c, i)| {
919 if c == '\n' {
920 Some(
921 snapshot.buffer_snapshot().anchor_after(i)
922 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
923 )
924 } else {
925 None
926 }
927 })
928 .collect::<Vec<_>>();
929 let existing_newlines = snapshot
930 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
931 .filter_map(|fold| {
932 if fold.placeholder.type_tag == Some(type_id) {
933 Some(fold.range.start..fold.range.end)
934 } else {
935 None
936 }
937 })
938 .collect::<Vec<_>>();
939
940 (new_newlines, existing_newlines)
941 });
942 self.folding_newlines = cx.spawn(async move |this, cx| {
943 let (new_newlines, existing_newlines) = task.await;
944 if new_newlines == existing_newlines {
945 return;
946 }
947 let placeholder = FoldPlaceholder {
948 render: Arc::new(move |_, _, cx| {
949 div()
950 .bg(cx.theme().status().hint_background)
951 .border_b_1()
952 .size_full()
953 .font(ThemeSettings::get_global(cx).buffer_font.clone())
954 .border_color(cx.theme().status().hint)
955 .child("\\n")
956 .into_any()
957 }),
958 constrain_width: false,
959 merge_adjacent: false,
960 type_tag: Some(type_id),
961 collapsed_text: None,
962 };
963 let creases = new_newlines
964 .into_iter()
965 .map(|range| Crease::simple(range, placeholder.clone()))
966 .collect();
967 this.update(cx, |this, cx| {
968 this.display_map.update(cx, |display_map, cx| {
969 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
970 display_map.fold(creases, cx);
971 });
972 })
973 .ok();
974 });
975 }
976
977 /// Load folds from the file_folds database table by file path.
978 /// Used when manually opening a file that was previously closed.
979 pub(super) fn load_folds_from_db(
980 &mut self,
981 workspace_id: WorkspaceId,
982 file_path: PathBuf,
983 window: &mut Window,
984 cx: &mut Context<Editor>,
985 ) {
986 if self.mode.is_minimap()
987 || WorkspaceSettings::get(None, cx).restore_on_startup
988 == RestoreOnStartupBehavior::EmptyTab
989 {
990 return;
991 }
992
993 let Some(folds) = EditorDb::global(cx)
994 .get_file_folds(workspace_id, &file_path)
995 .log_err()
996 else {
997 return;
998 };
999 if folds.is_empty() {
1000 return;
1001 }
1002
1003 let snapshot = self.buffer.read(cx).snapshot(cx);
1004 let snapshot_len = snapshot.len().0;
1005
1006 // Helper: search for fingerprint in buffer, return offset if found
1007 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
1008 let search_start = snapshot
1009 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
1010 .0;
1011 let search_end = snapshot_len.saturating_sub(fingerprint.len());
1012
1013 let mut byte_offset = search_start;
1014 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
1015 if byte_offset > search_end {
1016 break;
1017 }
1018 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
1019 return Some(byte_offset);
1020 }
1021 byte_offset += ch.len_utf8();
1022 }
1023 None
1024 };
1025
1026 let mut search_start = 0usize;
1027
1028 let valid_folds: Vec<_> = folds
1029 .into_iter()
1030 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
1031 let sfp = start_fp?;
1032 let efp = end_fp?;
1033 let efp_len = efp.len();
1034
1035 let start_matches = stored_start < snapshot_len
1036 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
1037 let efp_check_pos = stored_end.saturating_sub(efp_len);
1038 let end_matches = efp_check_pos >= stored_start
1039 && stored_end <= snapshot_len
1040 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
1041
1042 let (new_start, new_end) = if start_matches && end_matches {
1043 (stored_start, stored_end)
1044 } else if sfp == efp {
1045 let new_start = find_fingerprint(&sfp, search_start)?;
1046 let fold_len = stored_end - stored_start;
1047 let new_end = new_start + fold_len;
1048 (new_start, new_end)
1049 } else {
1050 let new_start = find_fingerprint(&sfp, search_start)?;
1051 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
1052 let new_end = efp_pos + efp_len;
1053 (new_start, new_end)
1054 };
1055
1056 search_start = new_end;
1057
1058 if new_end <= new_start {
1059 return None;
1060 }
1061
1062 Some(
1063 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
1064 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
1065 )
1066 })
1067 .collect();
1068
1069 if !valid_folds.is_empty() {
1070 self.fold_ranges(valid_folds, false, window, cx);
1071 }
1072 }
1073
1074 fn remove_folds_with<T: ToOffset + Clone>(
1075 &mut self,
1076 ranges: &[Range<T>],
1077 auto_scroll: bool,
1078 cx: &mut Context<Self>,
1079 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
1080 ) {
1081 if ranges.is_empty() {
1082 return;
1083 }
1084
1085 self.display_map.update(cx, update);
1086
1087 if auto_scroll {
1088 self.request_autoscroll(Autoscroll::fit(), cx);
1089 }
1090
1091 cx.notify();
1092 self.scrollbar_marker_state.dirty = true;
1093 self.active_indent_guides_state.dirty = true;
1094 }
1095}
1096