diff --git a/assets/settings/default.json b/assets/settings/default.json index c2c372fe51..bd73bcbf08 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -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, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2c334285f2..b15aa58709 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2169,8 +2169,8 @@ impl Editor { self.transact(cx, |this, cx| { let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = { let selections = this.selections.all::(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::(); + 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::(); - let trailing_whitespace_len = buffer - .chars_at(end) - .take_while(|c| c.is_whitespace() && *c != '\n') - .map(|c| c.len_utf8()) - .sum::(); + let trailing_whitespace_len = buffer + .chars_at(end) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); - 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() diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5cf79f9163..f2f7cb766f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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 + ˇ "}); } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 1a953b0bf2..832bb59222 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -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, #[serde(default)] pub show_whitespaces: Option, + #[serde(default)] + pub extend_comment_on_newline: Option, } #[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(target: &mut T, value: Option) { if let Some(value) = value { *target = value;