diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fb31e03674..b457e7fb2d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5790,20 +5790,21 @@ impl Editor { .collect::(); if !line_text_after_indent.is_empty() { - let block_prefix = language_scope + let block_prefixes = 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()); + .into_iter() + .chain(language_scope.documentation_comment()) + .filter(|comment| { + language_scope.override_name() == Some("comment") + && !comment.prefix.is_empty() + && !line_text_after_indent.starts_with(comment.end.as_ref()) + }) + .map(|comment| comment.prefix.as_ref()); let comment_prefixes = language_scope .line_comment_prefixes() .iter() .map(|p| p.as_ref()) - .chain(block_prefix) - .chain(doc_prefix) + .chain(block_prefixes) .map(|prefix| (prefix, false)); let all_prefixes = comment_prefixes.chain( language_scope diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 6dd6376b4c..bb6c2c372f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -23,7 +23,7 @@ use gpui::{ BackgroundExecutor, DismissEvent, Task, TaskExt, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions, div, }; -use indoc::indoc; +use indoc::{formatdoc, indoc}; use language::{ BracketPair, BracketPairConfig, Capability::{Read, ReadOnly, ReadWrite}, @@ -7038,22 +7038,8 @@ 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)); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx)); // Strips the comment prefix (with trailing space) from the joined-in line. cx.set_state(indoc! {" @@ -7117,22 +7103,30 @@ async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) { // 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ˇ + */ "}); } @@ -7208,6 +7202,92 @@ async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) { } } +#[gpui::test] +async fn test_join_lines_preserves_rust_operators(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx)); + + for insert_whitespace in [true, false] { + let separator = if insert_whitespace { " " } else { "" }; + for indent in ["", " ", "\t"] { + for (first_line, next_line) in [ + ("let value =", "*pointer;"), + ("let value =", "* pointer;"), + ("let value =", "**pointer;"), + ("let value =", "*指针;"), + ("let value = 2", "* 3;"), + ("let value = \"text", "*text\";"), + ("let value = r#\"text", "*text\"#;"), + ] { + cx.set_state(&formatdoc! {" + fn main() {{ + {first_line}ˇ + {indent}{next_line} + }}"}); + cx.update_editor(|editor, window, cx| { + editor.join_lines_impl(insert_whitespace, window, cx) + }); + cx.assert_editor_state(&formatdoc! {" + fn main() {{ + {first_line}ˇ{separator}{next_line} + }}"}); + } + } + } +} + +#[gpui::test] +async fn test_join_lines_rust_block_comments(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx)); + + for insert_whitespace in [true, false] { + let separator = if insert_whitespace { " " } else { "" }; + for start in ["/*", "/**", "/*!"] { + for next_line in ["* bar", "*bar"] { + cx.set_state(&formatdoc! {" + {start} + * fooˇ + {next_line} + */"}); + cx.update_editor(|editor, window, cx| { + editor.join_lines_impl(insert_whitespace, window, cx) + }); + cx.assert_editor_state(&formatdoc! {" + {start} + * fooˇ{separator}bar + */"}); + } + + cx.set_state(&formatdoc! {" + {start} + * fooˇ + * + */"}); + cx.update_editor(|editor, window, cx| { + editor.join_lines_impl(insert_whitespace, window, cx) + }); + cx.assert_editor_state(&formatdoc! {" + {start} + * fooˇ + */"}); + + cx.set_state(&formatdoc! {" + {start} + * fooˇ + */"}); + cx.update_editor(|editor, window, cx| { + editor.join_lines_impl(insert_whitespace, window, cx) + }); + cx.assert_editor_state(&formatdoc! {" + {start} + * fooˇ{separator}*/"}); + } + } +} + #[gpui::test] async fn test_custom_newlines_cause_no_false_positive_diffs( executor: BackgroundExecutor, diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 7edaf44509..f1b2a75d1c 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -26,7 +26,7 @@ use util::{path, test::marked_text_ranges}; pub use vim_test_context::*; use gpui::VisualTestContext; -use indoc::indoc; +use indoc::{formatdoc, indoc}; use project::FakeFs; use search::BufferSearchBar; use search::{ProjectSearchView, project_search}; @@ -621,6 +621,84 @@ async fn test_join_lines(cx: &mut gpui::TestAppContext) { "}); } +#[perf] +#[gpui::test] +async fn test_join_lines_rust_dereference(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + for dereference in ["*value.get()", "* value.get()"] { + let initial = formatdoc! {" + ˇlet another_value = unsafe {{ + {dereference} + }};"}; + let joined = formatdoc! {" + let another_value = unsafe {{ˇ {dereference} + }};"}; + let fully_joined = format!("let another_value = unsafe {{ {dereference}ˇ }};"); + + for keystrokes in [ + "shift-j", + "1 shift-j", + "2 shift-j", + "v j shift-j", + "shift-v j shift-j", + "j v k shift-j", + "j shift-v k shift-j", + ] { + cx.assert_binding_normal(keystrokes, &initial, &joined); + } + + for keystrokes in [ + "3 shift-j", + "v 2 j shift-j", + "shift-v 2 j shift-j", + "2 j v 2 k shift-j", + "2 j shift-v 2 k shift-j", + ] { + cx.assert_binding_normal(keystrokes, &initial, &fully_joined); + } + } +} + +#[perf] +#[gpui::test] +async fn test_join_lines_rust_dereference_without_whitespace(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + for dereference in ["*value.get()", "* value.get()"] { + let initial = formatdoc! {" + ˇlet another_value = unsafe {{ + {dereference} + }};"}; + let joined = formatdoc! {" + let another_value = unsafe {{ˇ{dereference} + }};"}; + let fully_joined = format!("let another_value = unsafe {{{dereference}ˇ}};"); + + for keystrokes in [ + "g shift-j", + "1 g shift-j", + "2 g shift-j", + "v j g shift-j", + "shift-v j g shift-j", + "j v k g shift-j", + "j shift-v k g shift-j", + ] { + cx.assert_binding_normal(keystrokes, &initial, &joined); + } + + for keystrokes in [ + "3 g shift-j", + "v 2 j g shift-j", + "shift-v 2 j g shift-j", + "2 j v 2 k g shift-j", + "2 j shift-v 2 k g shift-j", + ] { + cx.assert_binding_normal(keystrokes, &initial, &fully_joined); + } + } +} + #[cfg(target_os = "macos")] #[perf] #[gpui::test]