@@ -2268,23 +2268,38 @@ impl DisplaySnapshot {
&& !self.is_line_folded(MultiBufferRow(start.row))
{
let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
- let max_point = self.buffer_snapshot().max_point();
+ let snapshot = self.buffer_snapshot();
+ let max_point = snapshot.max_point();
let mut closing_row = None;
+ // End byte of the smallest syntactic node enclosing `buffer_row`.
+ // Used to tell standalone top-level comments (which terminate the
+ // fold) apart from unindented content inside a multi-line string
+ // or block comment belonging to the folded node (which does not).
+ let foldable_node_end = {
+ let row_start = Point::new(buffer_row.0, 0);
+ let row_end = Point::new(buffer_row.0, snapshot.line_len(buffer_row));
+ snapshot
+ .syntax_ancestor(row_start..row_end)
+ .map(|(_, range)| range.end)
+ };
+
for row in (buffer_row.0 + 1)..=max_point.row {
let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
if !line_indent.is_line_blank()
&& line_indent.raw_len() <= start_line_indent.raw_len()
{
- if self
- .buffer_snapshot()
+ let in_string_or_comment_scope = snapshot
.language_scope_at(Point::new(row, 0))
.is_some_and(|scope| {
matches!(
scope.override_name(),
Some("string") | Some("comment") | Some("comment.inclusive")
)
- })
+ });
+ if in_string_or_comment_scope
+ && let Some(end) = foldable_node_end
+ && Point::new(row, 0).to_offset(snapshot) < end
{
continue;
}
@@ -1636,6 +1636,182 @@ async fn test_fold_with_unindented_multiline_block_comment_includes_closing_brac
});
}
+#[gpui::test]
+async fn test_fold_preserves_top_level_comments_between_python_classes(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ let language = Arc::new(
+ Language::new(
+ LanguageConfig::default(),
+ Some(tree_sitter_python::LANGUAGE.into()),
+ )
+ .with_queries(LanguageQueries {
+ overrides: Some(Cow::from(indoc! {"
+ (comment) @comment.inclusive
+ (string) @string
+ "})),
+ ..Default::default()
+ })
+ .expect("Could not parse queries"),
+ );
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+ cx.set_state(indoc! {"
+ class Foo:
+ def bar(self):
+ pass
+
+
+ # SECTION SEPARATOR
+
+ class Baz:
+ def qux(self):
+ passˇ
+ "});
+
+ cx.update_editor(|editor, window, cx| {
+ editor.fold_at_level(&FoldAtLevel(1), window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ indoc! {"
+ class Foo:⋯
+
+
+ # SECTION SEPARATOR
+
+ class Baz:
+ def qux(self):
+ pass
+ "},
+ );
+ });
+}
+
+#[gpui::test]
+async fn test_fold_preserves_top_level_comments_between_rust_functions(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ let language = Arc::new(
+ Language::new(
+ LanguageConfig::default(),
+ Some(tree_sitter_rust::LANGUAGE.into()),
+ )
+ .with_queries(LanguageQueries {
+ overrides: Some(Cow::from(indoc! {"
+ [
+ (string_literal)
+ (raw_string_literal)
+ ] @string
+ [
+ (line_comment)
+ (block_comment)
+ ] @comment.inclusive
+ "})),
+ ..Default::default()
+ })
+ .expect("Could not parse queries"),
+ );
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+ cx.set_state(indoc! {"
+ fn foo() {
+ bar();
+ }
+
+
+ // SECTION SEPARATOR
+
+
+ fn baz() {
+ qux();ˇ
+ }
+ "});
+
+ cx.update_editor(|editor, window, cx| {
+ editor.fold_at_level(&FoldAtLevel(1), window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ indoc! {"
+ fn foo() {⋯
+ }
+
+
+ // SECTION SEPARATOR
+
+
+ fn baz() {
+ qux();
+ }
+ "},
+ );
+ });
+}
+
+#[gpui::test]
+async fn test_fold_terminates_at_top_level_multiline_string_between_python_classes(
+ cx: &mut TestAppContext,
+) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ let language = Arc::new(
+ Language::new(
+ LanguageConfig::default(),
+ Some(tree_sitter_python::LANGUAGE.into()),
+ )
+ .with_queries(LanguageQueries {
+ overrides: Some(Cow::from(indoc! {"
+ (comment) @comment.inclusive
+ (string) @string
+ "})),
+ ..Default::default()
+ })
+ .expect("Could not parse queries"),
+ );
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+ cx.set_state(indoc! {r#"
+ class Foo:
+ def bar(self):
+ pass
+
+
+ """
+ top-level docstring at zero indent
+ """
+
+
+ class Baz:
+ def qux(self):
+ passˇ
+ "#});
+
+ cx.update_editor(|editor, window, cx| {
+ editor.fold_at_level(&FoldAtLevel(1), window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ indoc! {r#"
+ class Foo:⋯
+
+
+ """
+ top-level docstring at zero indent
+ """
+
+
+ class Baz:
+ def qux(self):
+ pass
+ "#},
+ );
+ });
+}
+
#[gpui::test]
fn test_fold_at_level(cx: &mut TestAppContext) {
init_test(cx, |_| {});