Detailed changes
@@ -2269,6 +2269,29 @@ impl DisplaySnapshot {
.unwrap_or(false)
}
+ /// Returns the indent length of `row` if it starts with a closing bracket.
+ fn closing_bracket_indent_len(&self, row: u32) -> Option<u32> {
+ let snapshot = self.buffer_snapshot();
+ let indent_len = self
+ .line_indent_for_buffer_row(MultiBufferRow(row))
+ .raw_len();
+ let content_start = Point::new(row, indent_len);
+ let line_text: String = snapshot
+ .chars_at(content_start)
+ .take_while(|ch| *ch != '\n')
+ .collect();
+
+ let scope = snapshot.language_scope_at(Point::new(row, 0))?;
+ if scope
+ .brackets()
+ .any(|(pair, _)| line_text.starts_with(&pair.end))
+ {
+ return Some(indent_len);
+ }
+
+ None
+ }
+
#[instrument(skip_all)]
pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
let start =
@@ -2313,7 +2336,7 @@ impl DisplaySnapshot {
{
let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
let max_point = self.buffer_snapshot().max_point();
- let mut end = None;
+ let mut closing_row = None;
for row in (buffer_row.0 + 1)..=max_point.row {
let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
@@ -2333,32 +2356,33 @@ impl DisplaySnapshot {
continue;
}
- let prev_row = row - 1;
- end = Some(Point::new(
- prev_row,
- self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
- ));
+ closing_row = Some(row);
break;
}
}
- let mut row_before_line_breaks = end.unwrap_or(max_point);
- while row_before_line_breaks.row > start.row
- && self
- .buffer_snapshot()
- .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
- {
- row_before_line_breaks.row -= 1;
- }
+ let last_non_blank_row = |from_row: u32| -> Point {
+ let mut row = from_row;
+ while row > start.row && self.buffer_snapshot().is_line_blank(MultiBufferRow(row)) {
+ row -= 1;
+ }
+ Point::new(row, self.buffer_snapshot().line_len(MultiBufferRow(row)))
+ };
- row_before_line_breaks = Point::new(
- row_before_line_breaks.row,
- self.buffer_snapshot()
- .line_len(MultiBufferRow(row_before_line_breaks.row)),
- );
+ let end = if let Some(row) = closing_row {
+ if let Some(indent_len) = self.closing_bracket_indent_len(row) {
+ // Include newline and whitespace before closing delimiter,
+ // so it appears on the same display line as the fold placeholder
+ Point::new(row, indent_len)
+ } else {
+ last_non_blank_row(row - 1)
+ }
+ } else {
+ last_non_blank_row(max_point.row)
+ };
Some(Crease::Inline {
- range: start..row_before_line_breaks,
+ range: start..end,
placeholder: self.fold_placeholder.clone(),
render_toggle: None,
render_trailer: None,
@@ -23,7 +23,7 @@ use gpui::{
};
use indoc::indoc;
use language::{
- BracketPairConfig,
+ BracketPair, BracketPairConfig,
Capability::ReadWrite,
DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
LanguageConfigOverride, LanguageMatcher, LanguageName, LanguageQueries, Override, Point,
@@ -1121,7 +1121,93 @@ fn test_cancel(cx: &mut TestAppContext) {
}
#[gpui::test]
-fn test_fold_action(cx: &mut TestAppContext) {
+async fn test_fold_action(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
+ cx.set_state(indoc! {"
+ impl Foo {
+ // Hello!
+
+ fn a() {
+ 1
+ }
+
+ fn b() {
+ 2
+ }
+
+ fn c() {
+ 3
+ }
+ }ˇ
+ "});
+
+ cx.update_editor(|editor, window, cx| {
+ editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+ s.select_display_ranges([
+ DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
+ ]);
+ });
+ editor.fold(&Fold, window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ "
+ impl Foo {
+ // Hello!
+
+ fn a() {
+ 1
+ }
+
+ fn b() {⋯}
+
+ fn c() {⋯}
+ }
+ "
+ .unindent(),
+ );
+
+ editor.fold(&Fold, window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ "
+ impl Foo {⋯}
+ "
+ .unindent(),
+ );
+
+ editor.unfold_lines(&UnfoldLines, window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ "
+ impl Foo {
+ // Hello!
+
+ fn a() {
+ 1
+ }
+
+ fn b() {⋯}
+
+ fn c() {⋯}
+ }
+ "
+ .unindent(),
+ );
+
+ editor.unfold_lines(&UnfoldLines, window, cx);
+ assert_eq!(
+ editor.display_text(cx),
+ editor.buffer.read(cx).read(cx).text()
+ );
+ });
+}
+
+#[gpui::test]
+fn test_fold_action_without_language(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx.add_window(|window, cx| {
@@ -1440,6 +1526,36 @@ async fn test_fold_with_unindented_multiline_raw_string(cx: &mut TestAppContext)
});
}
+#[gpui::test]
+async fn test_fold_with_unindented_multiline_raw_string_includes_closing_bracket(
+ cx: &mut TestAppContext,
+) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), 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, |_| {});
@@ -1489,6 +1605,35 @@ async fn test_fold_with_unindented_multiline_block_comment(cx: &mut TestAppConte
});
}
+#[gpui::test]
+async fn test_fold_with_unindented_multiline_block_comment_includes_closing_bracket(
+ cx: &mut TestAppContext,
+) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), 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, |_| {});
@@ -23761,8 +23906,7 @@ async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
"
fn main() {
if a {
- b(⋯
- )
+ b(⋯)
} else {
e(
f
@@ -538,7 +538,7 @@ mod tests {
snapshot.is_line_folded(MultiBufferRow(0)),
"Indentation-based fold should work on the function"
);
- assert_eq!(editor.display_text(cx), "fn main() {⋯\n}\n",);
+ assert_eq!(editor.display_text(cx), "fn main() {⋯}\n",);
});
cx.update_editor(|editor, window, cx| {
@@ -666,7 +666,7 @@ mod tests {
snapshot.is_line_folded(MultiBufferRow(0)),
"Indentation-based fold should work again after switching back"
);
- assert_eq!(editor.display_text(cx), "fn main() {⋯\n}\n",);
+ assert_eq!(editor.display_text(cx), "fn main() {⋯}\n",);
});
}
@@ -2801,6 +2801,46 @@ pub fn rust_lang() -> Arc<Language> {
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
+ brackets: BracketPairConfig {
+ pairs: vec![
+ BracketPair {
+ start: "{".into(),
+ end: "}".into(),
+ close: true,
+ surround: false,
+ newline: true,
+ },
+ BracketPair {
+ start: "[".into(),
+ end: "]".into(),
+ close: true,
+ surround: false,
+ newline: true,
+ },
+ BracketPair {
+ start: "(".into(),
+ end: ")".into(),
+ close: true,
+ surround: false,
+ newline: true,
+ },
+ BracketPair {
+ start: "<".into(),
+ end: ">".into(),
+ close: false,
+ surround: false,
+ newline: true,
+ },
+ BracketPair {
+ start: "\"".into(),
+ end: "\"".into(),
+ close: true,
+ surround: false,
+ newline: false,
+ },
+ ],
+ ..Default::default()
+ },
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),