Strip comment and list prefixes in vim: join lines (#49295)
Closes #ISSUE Before you mark this PR as ready for review, make sure that you have: - [ ] Added a solid test coverage and/or screenshots from doing manual testing - [ ] Done a self-review taking into account security and performance aspects - [ ] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Improved the behavior of the `vim: join lines` and `editor: join lines`. Comment and other language specific line prefixes are now stripped from the join line
This commit is contained in:
1 parent
b0ffa2f642
commit
4b160fc862
2 files changed
+216
-7
No files matched your search
@@ -11334,14 +11334,66 @@ impl Editor {
|
||||
let end_of_line = Point::new(row.0, snapshot.line_len(row));
|
||||
let next_line_row = row.next_row();
|
||||
let indent = snapshot.indent_size_for_line(next_line_row);
|
||||
let start_of_next_line = Point::new(next_line_row.0, indent.len);
|
||||
let mut join_start_column = indent.len;
|
||||
|
||||
let replace =
|
||||
if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if let Some(language_scope) =
|
||||
snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
|
||||
{
|
||||
let line_end =
|
||||
Point::new(next_line_row.0, snapshot.line_len(next_line_row));
|
||||
let line_text_after_indent = snapshot
|
||||
.text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
|
||||
.collect::<String>();
|
||||
|
||||
if !line_text_after_indent.is_empty() {
|
||||
let block_prefix = language_scope
|
||||
.block_comment()
|
||||
.map(|c| c.prefix.as_ref())
|
||||
.filter(|p| !p.is_empty());
|
||||
let doc_prefix = language_scope
|
||||
.documentation_comment()
|
||||
.map(|c| c.prefix.as_ref())
|
||||
.filter(|p| !p.is_empty());
|
||||
let all_prefixes = language_scope
|
||||
.line_comment_prefixes()
|
||||
.iter()
|
||||
.map(|p| p.as_ref())
|
||||
.chain(block_prefix)
|
||||
.chain(doc_prefix)
|
||||
.chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
|
||||
|
||||
let mut longest_prefix_len = None;
|
||||
for prefix in all_prefixes {
|
||||
let trimmed = prefix.trim_end();
|
||||
if line_text_after_indent.starts_with(trimmed) {
|
||||
let candidate_len =
|
||||
if line_text_after_indent.starts_with(prefix) {
|
||||
prefix.len()
|
||||
} else {
|
||||
trimmed.len()
|
||||
};
|
||||
if longest_prefix_len.map_or(true, |len| candidate_len > len) {
|
||||
longest_prefix_len = Some(candidate_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(prefix_len) = longest_prefix_len {
|
||||
join_start_column =
|
||||
join_start_column.saturating_add(prefix_len as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let start_of_next_line = Point::new(next_line_row.0, join_start_column);
|
||||
|
||||
let replace = if snapshot.line_len(next_line_row) > join_start_column
|
||||
&& insert_whitespace
|
||||
{
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
|
||||
|
||||
@@ -5014,6 +5014,163 @@ async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &m
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into(), "/// ".into()],
|
||||
documentation_comment: Some(BlockCommentConfig {
|
||||
start: "/*".into(),
|
||||
end: "*/".into(),
|
||||
prefix: "* ".into(),
|
||||
tab_size: 1,
|
||||
}),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
// Strips the comment prefix (with trailing space) from the joined-in line.
|
||||
cx.set_state(indoc! {"
|
||||
// ˇfoo
|
||||
// bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// fooˇ bar
|
||||
"});
|
||||
|
||||
// Strips the longer doc-comment prefix when both `//` and `///` match.
|
||||
cx.set_state(indoc! {"
|
||||
/// ˇfoo
|
||||
/// bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/// fooˇ bar
|
||||
"});
|
||||
|
||||
// Does not strip when the second line is a regular line (no comment prefix).
|
||||
cx.set_state(indoc! {"
|
||||
// ˇfoo
|
||||
bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// fooˇ bar
|
||||
"});
|
||||
|
||||
// No-whitespace join also strips the comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
// ˇfoo
|
||||
// bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// fooˇbar
|
||||
"});
|
||||
|
||||
// Strips even when the joined-in line is just the bare prefix (no trailing space).
|
||||
cx.set_state(indoc! {"
|
||||
// ˇfoo
|
||||
//
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// fooˇ
|
||||
"});
|
||||
|
||||
// Mixed line comment prefix types: the longer matching prefix is stripped.
|
||||
cx.set_state(indoc! {"
|
||||
// ˇfoo
|
||||
/// bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// fooˇ bar
|
||||
"});
|
||||
|
||||
// Strips block comment body prefix (`* `) from the joined-in line.
|
||||
cx.set_state(indoc! {"
|
||||
* ˇfoo
|
||||
* bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
* fooˇ bar
|
||||
"});
|
||||
|
||||
// Strips bare block comment body prefix (`*` without trailing space).
|
||||
cx.set_state(indoc! {"
|
||||
* ˇfoo
|
||||
*
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
* fooˇ
|
||||
"});
|
||||
}
|
||||
|
||||
{
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
unordered_list: vec!["- ".into(), "* ".into(), "+ ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
|
||||
// Strips the `- ` list marker from the joined-in line.
|
||||
cx.set_state(indoc! {"
|
||||
- ˇfoo
|
||||
- bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
- fooˇ bar
|
||||
"});
|
||||
|
||||
// Strips the `* ` list marker from the joined-in line.
|
||||
cx.set_state(indoc! {"
|
||||
* ˇfoo
|
||||
* bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
* fooˇ bar
|
||||
"});
|
||||
|
||||
// Strips the `+ ` list marker from the joined-in line.
|
||||
cx.set_state(indoc! {"
|
||||
+ ˇfoo
|
||||
+ bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
+ fooˇ bar
|
||||
"});
|
||||
|
||||
// No-whitespace join also strips the list marker.
|
||||
cx.set_state(indoc! {"
|
||||
- ˇfoo
|
||||
- bar
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
- fooˇbar
|
||||
"});
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_custom_newlines_cause_no_false_positive_diffs(
|
||||
executor: BackgroundExecutor,
|
||||
|
||||
Reference in new issue
Block a user