Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:32:56.694Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

fold.rs

1096 lines · 38.6 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.