Add FoldFunctionBodies editor action (#21504)

Cole Miller and Conrad created

Related to #19424

This uses the new text object support, so will only work for languages
that have `textobjects.scm`. It does not integrate with
indentation-based folding for now, and the syntax-based folds don't have
matching fold markers in the gutter (unless they are folded).

Release Notes:

- Add an editor action to fold all function bodies

Co-authored-by: Conrad <conrad@zed.dev>

Change summary

crates/editor/src/actions.rs  |  1 +
crates/editor/src/editor.rs   | 17 +++++++++++++++++
crates/editor/src/element.rs  |  1 +
crates/language/src/buffer.rs |  8 ++++++++
4 files changed, 27 insertions(+)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -248,6 +248,7 @@ gpui::actions!(
         FindAllReferences,
         Fold,
         FoldAll,
+        FoldFunctionBodies,
         FoldRecursive,
         FoldSelectedRanges,
         ToggleFold,

crates/editor/src/editor.rs 🔗

@@ -11097,6 +11097,23 @@ impl Editor {
         self.fold_creases(fold_ranges, true, cx);
     }
 
+    pub fn fold_function_bodies(
+        &mut self,
+        _: &actions::FoldFunctionBodies,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let Some((_, _, buffer)) = snapshot.as_singleton() else {
+            return;
+        };
+        let creases = buffer
+            .function_body_fold_ranges(0..buffer.len())
+            .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
+            .collect();
+
+        self.fold_creases(creases, true, cx);
+    }
+
     pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
         let mut to_fold = Vec::new();
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));

crates/editor/src/element.rs 🔗

@@ -342,6 +342,7 @@ impl EditorElement {
         register_action(view, cx, Editor::fold);
         register_action(view, cx, Editor::fold_at_level);
         register_action(view, cx, Editor::fold_all);
+        register_action(view, cx, Editor::fold_function_bodies);
         register_action(view, cx, Editor::fold_at);
         register_action(view, cx, Editor::fold_recursive);
         register_action(view, cx, Editor::toggle_fold);

crates/language/src/buffer.rs 🔗

@@ -3355,6 +3355,14 @@ impl BufferSnapshot {
         })
     }
 
+    pub fn function_body_fold_ranges<T: ToOffset>(
+        &self,
+        within: Range<T>,
+    ) -> impl Iterator<Item = Range<usize>> + '_ {
+        self.text_object_ranges(within, TreeSitterOptions::default())
+            .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
+    }
+
     /// For each grammar in the language, runs the provided
     /// [`tree_sitter::Query`] against the given range.
     pub fn matches(