editor: Add language setting for comment continuations (#2605)

Per @JosephTLyons request I've added a language setting for comment
continuations.

Release Notes:

- Added a language setting for comment continuations.
This commit is contained in:
Piotr Osiewicz 2023-06-13 18:59:46 +02:00 committed by GitHub
parent aedef7bc58
commit b272db9e21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 72 deletions

View File

@ -108,6 +108,8 @@
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
// Whether to start a new line with a comment when a previous line is a comment as well.
"extend_comment_on_newline": true,
// Whether or not to ensure there's a single newline at the end of a buffer
// when saving it.
"ensure_final_newline_on_save": true,

View File

@ -2169,8 +2169,8 @@ impl Editor {
self.transact(cx, |this, cx| {
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
let selections = this.selections.all::<usize>(cx);
let buffer = this.buffer.read(cx).snapshot(cx);
let multi_buffer = this.buffer.read(cx);
let buffer = multi_buffer.snapshot(cx);
selections
.iter()
.map(|selection| {
@ -2181,70 +2181,74 @@ impl Editor {
let end = selection.end;
let is_cursor = start == end;
let language_scope = buffer.language_scope_at(start);
let (comment_delimiter, insert_extra_newline) =
if let Some(language) = &language_scope {
let leading_whitespace_len = buffer
.reversed_chars_at(start)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let (comment_delimiter, insert_extra_newline) = if let Some(language) =
&language_scope
{
let leading_whitespace_len = buffer
.reversed_chars_at(start)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let trailing_whitespace_len = buffer
.chars_at(end)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let trailing_whitespace_len = buffer
.chars_at(end)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let insert_extra_newline =
language.brackets().any(|(pair, enabled)| {
let pair_start = pair.start.trim_end();
let pair_end = pair.end.trim_start();
let insert_extra_newline =
language.brackets().any(|(pair, enabled)| {
let pair_start = pair.start.trim_end();
let pair_end = pair.end.trim_start();
enabled
&& pair.newline
&& buffer.contains_str_at(
end + trailing_whitespace_len,
pair_end,
)
&& buffer.contains_str_at(
(start - leading_whitespace_len)
.saturating_sub(pair_start.len()),
pair_start,
)
});
// Comment extension on newline is allowed only for cursor selections
let comment_delimiter =
language.line_comment_prefix().filter(|_| is_cursor);
let comment_delimiter = if let Some(delimiter) = comment_delimiter {
buffer
.buffer_line_for_row(start_point.row)
.is_some_and(|(snapshot, range)| {
let mut index_of_first_non_whitespace = 0;
let line_starts_with_comment = snapshot
.chars_for_range(range)
.skip_while(|c| {
let should_skip = c.is_whitespace();
if should_skip {
index_of_first_non_whitespace += 1;
}
should_skip
})
.take(delimiter.len())
.eq(delimiter.chars());
let cursor_is_placed_after_comment_marker =
index_of_first_non_whitespace + delimiter.len()
<= start_point.column as usize;
line_starts_with_comment
&& cursor_is_placed_after_comment_marker
})
.then(|| delimiter.clone())
} else {
None
};
(comment_delimiter, insert_extra_newline)
enabled
&& pair.newline
&& buffer.contains_str_at(
end + trailing_whitespace_len,
pair_end,
)
&& buffer.contains_str_at(
(start - leading_whitespace_len)
.saturating_sub(pair_start.len()),
pair_start,
)
});
// Comment extension on newline is allowed only for cursor selections
let comment_delimiter = language.line_comment_prefix().filter(|_| {
let is_comment_extension_enabled =
multi_buffer.settings_at(0, cx).extend_comment_on_newline;
is_cursor && is_comment_extension_enabled
});
let comment_delimiter = if let Some(delimiter) = comment_delimiter {
buffer
.buffer_line_for_row(start_point.row)
.is_some_and(|(snapshot, range)| {
let mut index_of_first_non_whitespace = 0;
let line_starts_with_comment = snapshot
.chars_for_range(range)
.skip_while(|c| {
let should_skip = c.is_whitespace();
if should_skip {
index_of_first_non_whitespace += 1;
}
should_skip
})
.take(delimiter.len())
.eq(delimiter.chars());
let cursor_is_placed_after_comment_marker =
index_of_first_non_whitespace + delimiter.len()
<= start_point.column as usize;
line_starts_with_comment
&& cursor_is_placed_after_comment_marker
})
.then(|| delimiter.clone())
} else {
(None, false)
None
};
(comment_delimiter, insert_extra_newline)
} else {
(None, false)
};
let capacity_for_delimiter = comment_delimiter
.as_deref()

View File

@ -1732,26 +1732,40 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
},
None,
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
{
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
// Fooˇ
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
// Foo
//ˇ
"});
// Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
cx.set_state(indoc! {"
// Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
cx.set_state(indoc! {"
ˇ// Foo
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
ˇ// Foo
"});
}
// Ensure that comment continuations can be disabled.
update_test_settings(cx, |settings| {
settings.defaults.extend_comment_on_newline = Some(false);
});
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
// Fooˇ
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
ˇ// Foo
// Foo
ˇ
"});
}

View File

@ -51,6 +51,7 @@ pub struct LanguageSettings {
pub enable_language_server: bool,
pub show_copilot_suggestions: bool,
pub show_whitespaces: ShowWhitespaceSetting,
pub extend_comment_on_newline: bool,
}
#[derive(Clone, Debug, Default)]
@ -95,6 +96,8 @@ pub struct LanguageSettingsContent {
pub show_copilot_suggestions: Option<bool>,
#[serde(default)]
pub show_whitespaces: Option<ShowWhitespaceSetting>,
#[serde(default)]
pub extend_comment_on_newline: Option<bool>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
@ -340,7 +343,10 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
src.show_copilot_suggestions,
);
merge(&mut settings.show_whitespaces, src.show_whitespaces);
merge(
&mut settings.extend_comment_on_newline,
src.extend_comment_on_newline,
);
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;