diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 83379e13aed32..474655700cf3f 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -80,6 +80,8 @@ pub struct ConfirmCodeAction { pub struct ToggleComments { #[serde(default)] pub advance_downwards: bool, + #[serde(default)] + pub ignore_indent: bool, } #[derive(PartialEq, Clone, Deserialize, Default)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2e88df6b9236b..16ba3d8b8ba25 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8664,14 +8664,22 @@ impl Editor { let snapshot = this.buffer.read(cx).read(cx); let empty_str: Arc = Arc::default(); let mut suffixes_inserted = Vec::new(); + let ignore_indent = action.ignore_indent; fn comment_prefix_range( snapshot: &MultiBufferSnapshot, row: MultiBufferRow, comment_prefix: &str, comment_prefix_whitespace: &str, + ignore_indent: bool, ) -> Range { - let start = Point::new(row.0, snapshot.indent_size_for_line(row).len); + let indent_size = if ignore_indent { + 0 + } else { + snapshot.indent_size_for_line(row).len + }; + + let start = Point::new(row.0, indent_size); let mut line_bytes = snapshot .bytes_in_range(start..snapshot.max_point()) @@ -8767,7 +8775,16 @@ impl Editor { } // If the language has line comments, toggle those. - let full_comment_prefixes = language.line_comment_prefixes(); + let mut full_comment_prefixes = language.line_comment_prefixes().to_vec(); + + // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes + if ignore_indent { + full_comment_prefixes = full_comment_prefixes + .into_iter() + .map(|s| Arc::from(s.trim_end())) + .collect(); + } + if !full_comment_prefixes.is_empty() { let first_prefix = full_comment_prefixes .first() @@ -8794,6 +8811,7 @@ impl Editor { row, &prefix[..trimmed_prefix_len], &prefix[trimmed_prefix_len..], + ignore_indent, ) }) .max_by_key(|range| range.end.column - range.start.column) @@ -8834,6 +8852,7 @@ impl Editor { start_row, comment_prefix, comment_prefix_whitespace, + ignore_indent, ); let suffix_range = comment_suffix_range( snapshot.deref(), diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d56b22b454208..02583889bca84 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8533,6 +8533,131 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { "}); } +#[gpui::test] +async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + let language = Arc::new(Language::new( + LanguageConfig { + line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()], + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + )); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + + let toggle_comments = &ToggleComments { + advance_downwards: false, + ignore_indent: true, + }; + + // If multiple selections intersect a line, the line is only toggled once. + cx.set_state(indoc! {" + fn a() { + // «b(); + // c(); + // ˇ» d(); + } + "}); + + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + «b(); + c(); + ˇ» d(); + } + "}); + + // The comment prefix is inserted at the beginning of each line + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + // «b(); + // c(); + // ˇ» d(); + } + "}); + + // If a selection ends at the beginning of a line, that line is not toggled. + cx.set_selections_state(indoc! {" + fn a() { + // b(); + // «c(); + ˇ»// d(); + } + "}); + + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + // b(); + «c(); + ˇ»// d(); + } + "}); + + // If a selection span a single line and is empty, the line is toggled. + cx.set_state(indoc! {" + fn a() { + a(); + b(); + ˇ + } + "}); + + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + a(); + b(); + //ˇ + } + "}); + + // If a selection span multiple lines, empty lines are not toggled. + cx.set_state(indoc! {" + fn a() { + «a(); + + c();ˇ» + } + "}); + + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + // «a(); + + // c();ˇ» + } + "}); + + // If a selection includes multiple comment prefixes, all lines are uncommented. + cx.set_state(indoc! {" + fn a() { + // «a(); + /// b(); + //! c();ˇ» + } + "}); + + cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx)); + + cx.assert_editor_state(indoc! {" + fn a() { + «a(); + b(); + c();ˇ» + } + "}); +} + #[gpui::test] async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -8554,6 +8679,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) let toggle_comments = &ToggleComments { advance_downwards: true, + ignore_indent: false, }; // Single cursor on one line -> advance