editor: Fix folding for unindented multiline strings and comments (#50049)

Closes #5057

## Summary

The indent-based code fold detection does not account for lines inside
multiline strings or block comments that have less indentation than the
surrounding code. This causes the fold scanner to think the enclosing
block ends prematurely.

For example, folding `fn main()` here would fail because the raw string
content at indent level 0 gets treated as the end of the function body:

``` rust
fn main() {
    let s = r#"
unindented content
"#;
}  
```

This PR checks whether a low-indent line falls inside a string or
comment override region and skips it if so. This works across all
languages that define `@string` or `@comment` overrides in their
`overrides.scm`.

## Before


https://github.com/user-attachments/assets/a08e6bf8-4f25-4211-8a46-8f6da7e49247

## After


https://github.com/user-attachments/assets/cd5b36db-6d4d-420b-9d60-79f9fad8638e

## Test Plan

- Added `test_fold_with_unindented_multiline_raw_string`
- Added `test_fold_with_unindented_multiline_block_comment`
- All existing fold tests pass
- Manually tested both Rust and Python examples

Release Notes:

- Fixed code folding incorrectly collapsing when multiline strings or
block comments contained unindented content
This commit is contained in:
Ryan Walker authored and GitHub committed 2026-03-19 19:44:31 +05:30
1 parent 8f8dfab769
commit 85af3c7e43
2 files changed
+114 -1

No files matched your search

+13
View File
@@ -2320,6 +2320,19 @@ impl DisplaySnapshot {
if !line_indent.is_line_blank()
&& line_indent.raw_len() <= start_line_indent.raw_len()
{
if self
.buffer_snapshot()
.language_scope_at(Point::new(row, 0))
.is_some_and(|scope| {
matches!(
scope.override_name(),
Some("string") | Some("comment") | Some("comment.inclusive")
)
})
{
continue;
}
let prev_row = row - 1;
end = Some(Point::new(
prev_row,
+101 -1
View File
@@ -26,7 +26,7 @@ use language::{
BracketPairConfig,
Capability::ReadWrite,
DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
LanguageConfigOverride, LanguageMatcher, LanguageName, LanguageQueries, Override, Point,
language_settings::{
CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
},
@@ -51,6 +51,7 @@ use settings::{
IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
ProjectSettingsContent, SearchSettingsContent, SettingsContent, SettingsStore,
};
use std::borrow::Cow;
use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
use std::{
iter,
@@ -1324,6 +1325,105 @@ fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_fold_with_unindented_multiline_raw_string(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 main() {
let s = r#\"
a
b
c
\"#;
}ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.fold_at_level(&FoldAtLevel(1), window, cx);
assert_eq!(
editor.display_text(cx),
indoc! {"
fn main() {⋯
}
"},
);
});
}
#[gpui::test]
async fn test_fold_with_unindented_multiline_block_comment(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 main() {
let x = 1;
/*
unindented comment line
*/
}ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.fold_at_level(&FoldAtLevel(1), window, cx);
assert_eq!(
editor.display_text(cx),
indoc! {"
fn main() {⋯
}
"},
);
});
}
#[gpui::test]
fn test_fold_at_level(cx: &mut TestAppContext) {
init_test(cx, |_| {});