editor: Support rewrap in block comments (#34418)

This updates `editor: rewrap` to work within doc comments, based on the
code that extends such comments on newline. I added some tests, and I've
tested it out in JS, C and PHP. (Though PHP depends on
https://github.com/zed-extensions/php/pull/40)

Closes #19794
Closes #18221

**Caveat:**
~~This will not rewrap an existing single-line block comment, such as
the one provided in #18221:~~ this will now rewrap as expected
```c
/* we can triangulate any convex polygon by picking a vertex and connecting it to the next two vertices; we first read two vertices, and then, for every subsequent vertex, we can form a triangle by connecting it to the first and previous vertex */
```
However, it will rewrap a similar comment if it is shaped like a doc
comment. In other words, this will rewrap as expected:
```c
/* 
 * we can triangulate any convex polygon by picking a vertex and connecting it to the next two vertices; we first read two vertices, and then, for every subsequent vertex, we can form a triangle by connecting it to the first and previous vertex 
 */
```

This seems like a reasonable improvement and limitation to me,
especially as a first step.

cc @smitbarmase because I think that you've been making a lot of the
`newline` and `rewrap` changes recently. (Thank you for those, by the
way!)

Release Notes:

- Added support for rewrap in block comments.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
claytonrcarter
2025-09-01 20:00:01 +00:00
committed by GitHub
co-authored by Smit Barmase
parent 965dbc988f
commit 2ba25b5c94
2 changed files with 581 additions and 59 deletions
+417 -8
View File
@@ -5561,14 +5561,18 @@ async fn test_rewrap(cx: &mut TestAppContext) {
},
None,
));
let rust_language = Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
line_comments: vec!["// ".into(), "/// ".into()],
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
let rust_language = Arc::new(
Language::new(
LanguageConfig {
name: "Rust".into(),
line_comments: vec!["// ".into(), "/// ".into()],
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
.unwrap(),
);
let plaintext_language = Arc::new(Language::new(
LanguageConfig {
@@ -5884,6 +5888,411 @@ async fn test_rewrap(cx: &mut TestAppContext) {
}
}
#[gpui::test]
async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.languages.0.extend([(
"Rust".into(),
LanguageSettingsContent {
allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
preferred_line_length: Some(40),
..Default::default()
},
)])
});
let mut cx = EditorTestContext::new(cx).await;
let rust_lang = Arc::new(
Language::new(
LanguageConfig {
name: "Rust".into(),
line_comments: vec!["// ".into()],
block_comment: Some(BlockCommentConfig {
start: "/*".into(),
end: "*/".into(),
prefix: "* ".into(),
tab_size: 1,
}),
documentation_comment: Some(BlockCommentConfig {
start: "/**".into(),
end: "*/".into(),
prefix: "* ".into(),
tab_size: 1,
}),
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
.unwrap(),
);
// regular block comment
assert_rewrap(
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
/*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
"},
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// indent is respected
assert_rewrap(
indoc! {"
{}
/*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
"},
indoc! {"
{}
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// short block comments with inline delimiters
assert_rewrap(
indoc! {"
/*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
/*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
"},
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// multiline block comment with inline start/end delimiters
assert_rewrap(
indoc! {"
/*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit. */
"},
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// block comment rewrap still respects paragraph bounds
assert_rewrap(
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*
* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
"},
indoc! {"
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*
* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// documentation comments
assert_rewrap(
indoc! {"
/**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
/**
*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
"},
indoc! {"
/**
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/**
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
"},
rust_lang.clone(),
&mut cx,
);
// different, adjacent comments
assert_rewrap(
indoc! {"
/**
*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
/*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
//ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
"},
indoc! {"
/**
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
//ˇ Lorem ipsum dolor sit amet,
// consectetur adipiscing elit.
"},
rust_lang.clone(),
&mut cx,
);
// selection w/ single short block comment
assert_rewrap(
indoc! {"
«/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
"},
indoc! {"
«/*
* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/ˇ»
"},
rust_lang.clone(),
&mut cx,
);
// rewrapping a single comment w/ abutting comments
assert_rewrap(
indoc! {"
/* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
"},
indoc! {"
/*
* ˇLorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
"},
rust_lang.clone(),
&mut cx,
);
// selection w/ non-abutting short block comments
assert_rewrap(
indoc! {"
«/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
"},
indoc! {"
«/*
* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/ˇ»
"},
rust_lang.clone(),
&mut cx,
);
// selection of multiline block comments
assert_rewrap(
indoc! {"
«/* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit. */ˇ»
"},
indoc! {"
«/*
* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/ˇ»
"},
rust_lang.clone(),
&mut cx,
);
// partial selection of multiline block comments
assert_rewrap(
indoc! {"
«/* Lorem ipsum dolor sit amet,ˇ»
* consectetur adipiscing elit. */
/* Lorem ipsum dolor sit amet,
«* consectetur adipiscing elit. */ˇ»
"},
indoc! {"
«/*
* Lorem ipsum dolor sit amet,ˇ»
* consectetur adipiscing elit. */
/* Lorem ipsum dolor sit amet,
«* consectetur adipiscing elit.
*/ˇ»
"},
rust_lang.clone(),
&mut cx,
);
// selection w/ abutting short block comments
// TODO: should not be combined; should rewrap as 2 comments
assert_rewrap(
indoc! {"
«/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
"},
// desired behavior:
// indoc! {"
// «/*
// * Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// */
// /*
// * Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// */ˇ»
// "},
// actual behaviour:
indoc! {"
«/*
* Lorem ipsum dolor sit amet,
* consectetur adipiscing elit. Lorem
* ipsum dolor sit amet, consectetur
* adipiscing elit.
*/ˇ»
"},
rust_lang.clone(),
&mut cx,
);
// TODO: same as above, but with delimiters on separate line
// assert_rewrap(
// indoc! {"
// «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
// */
// /*
// * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
// "},
// // desired:
// // indoc! {"
// // «/*
// // * Lorem ipsum dolor sit amet,
// // * consectetur adipiscing elit.
// // */
// // /*
// // * Lorem ipsum dolor sit amet,
// // * consectetur adipiscing elit.
// // */ˇ»
// // "},
// // actual: (but with trailing w/s on the empty lines)
// indoc! {"
// «/*
// * Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// *
// */
// /*
// *
// * Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// */ˇ»
// "},
// rust_lang.clone(),
// &mut cx,
// );
// TODO these are unhandled edge cases; not correct, just documenting known issues
assert_rewrap(
indoc! {"
/*
//ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
/*
//ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
/*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
"},
// desired:
// indoc! {"
// /*
// *ˇ Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// */
// /*
// *ˇ Lorem ipsum dolor sit amet,
// * consectetur adipiscing elit.
// */
// /*
// *ˇ Lorem ipsum dolor sit amet
// */ /* consectetur adipiscing elit. */
// "},
// actual:
indoc! {"
/*
//ˇ Lorem ipsum dolor sit amet,
// consectetur adipiscing elit.
*/
/*
* //ˇ Lorem ipsum dolor sit amet,
* consectetur adipiscing elit.
*/
/*
*ˇ Lorem ipsum dolor sit amet */ /*
* consectetur adipiscing elit.
*/
"},
rust_lang,
&mut cx,
);
#[track_caller]
fn assert_rewrap(
unwrapped_text: &str,
wrapped_text: &str,
language: Arc<Language>,
cx: &mut EditorTestContext,
) {
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(unwrapped_text);
cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
cx.assert_editor_state(wrapped_text);
}
}
#[gpui::test]
async fn test_hard_wrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});