vim: Copy comment to new lines with o/O (#19766)

Conrad Irwin created

Co-Authored-By: Kurt Wolf <kurtwolfbuilds@gmail.com>

Closes: #4691

Closes #ISSUE

Release Notes:

- vim: o/O now respect `extend_comment_on_newline`

Change summary

crates/editor/src/editor.rs                       |  4 +
crates/editor/src/test/editor_lsp_test_context.rs |  1 
crates/multi_buffer/src/multi_buffer.rs           | 24 +++++++
crates/vim/src/normal.rs                          | 52 +++++++++++-----
crates/vim/test_data/test_o_comment.json          |  8 ++
5 files changed, 73 insertions(+), 16 deletions(-)

Detailed changes

crates/editor/src/editor.rs ๐Ÿ”—

@@ -223,6 +223,7 @@ pub fn render_parsed_markdown(
                 }
             }),
     );
+    // hello
 
     let mut links = Vec::new();
     let mut link_ranges = Vec::new();
@@ -3784,6 +3785,9 @@ impl Editor {
     pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
         let buffer = self.buffer.read(cx);
         let snapshot = buffer.snapshot(cx);
+        //
+        //
+        //
 
         let mut edits = Vec::new();
         let mut rows = Vec::new();

crates/editor/src/test/editor_lsp_test_context.rs ๐Ÿ”—

@@ -123,6 +123,7 @@ impl EditorLspTestContext {
                     path_suffixes: vec!["rs".to_string()],
                     ..Default::default()
                 },
+                line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
                 ..Default::default()
             },
             Some(tree_sitter_rust::LANGUAGE.into()),

crates/multi_buffer/src/multi_buffer.rs ๐Ÿ”—

@@ -2862,6 +2862,30 @@ impl MultiBufferSnapshot {
         }
     }
 
+    pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &AppContext) -> String {
+        let mut indent = self.indent_size_for_line(row).chars().collect::<String>();
+
+        if self.settings_at(0, cx).extend_comment_on_newline {
+            if let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) {
+                let delimiters = language_scope.line_comment_prefixes();
+                for delimiter in delimiters {
+                    if *self
+                        .chars_at(Point::new(row.0, indent.len() as u32))
+                        .take(delimiter.chars().count())
+                        .collect::<String>()
+                        .as_str()
+                        == **delimiter
+                    {
+                        indent.push_str(&delimiter);
+                        break;
+                    }
+                }
+            }
+        }
+
+        indent
+    }
+
     pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> {
         while row.0 > 0 {
             row.0 -= 1;

crates/vim/src/normal.rs ๐Ÿ”—

@@ -328,14 +328,18 @@ impl Vim {
                     .into_iter()
                     .map(|selection| selection.start.row)
                     .collect();
-                let edits = selection_start_rows.into_iter().map(|row| {
-                    let indent = snapshot
-                        .indent_size_for_line(MultiBufferRow(row))
-                        .chars()
-                        .collect::<String>();
-                    let start_of_line = Point::new(row, 0);
-                    (start_of_line..start_of_line, indent + "\n")
-                });
+                let edits = selection_start_rows
+                    .into_iter()
+                    .map(|row| {
+                        let indent = snapshot
+                            .indent_and_comment_for_line(MultiBufferRow(row), cx)
+                            .chars()
+                            .collect::<String>();
+
+                        let start_of_line = Point::new(row, 0);
+                        (start_of_line..start_of_line, indent + "\n")
+                    })
+                    .collect::<Vec<_>>();
                 editor.edit_with_autoindent(edits, cx);
                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                     s.move_cursors_with(|map, cursor, _| {
@@ -361,14 +365,18 @@ impl Vim {
                     .into_iter()
                     .map(|selection| selection.end.row)
                     .collect();
-                let edits = selection_end_rows.into_iter().map(|row| {
-                    let indent = snapshot
-                        .indent_size_for_line(MultiBufferRow(row))
-                        .chars()
-                        .collect::<String>();
-                    let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
-                    (end_of_line..end_of_line, "\n".to_string() + &indent)
-                });
+                let edits = selection_end_rows
+                    .into_iter()
+                    .map(|row| {
+                        let indent = snapshot
+                            .indent_and_comment_for_line(MultiBufferRow(row), cx)
+                            .chars()
+                            .collect::<String>();
+
+                        let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
+                        (end_of_line..end_of_line, "\n".to_string() + &indent)
+                    })
+                    .collect::<Vec<_>>();
                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
                     s.maybe_move_cursors_with(|map, cursor, goal| {
                         Motion::CurrentLine.move_point(
@@ -1414,4 +1422,16 @@ mod test {
             .await
             .assert_eq("th th\nth th\nth th\nth th\nth th\nห‡th th\n");
     }
+
+    #[gpui::test]
+    async fn test_o_comment(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.set_neovim_option("filetype=rust").await;
+
+        cx.set_shared_state("// helloห‡\n").await;
+        cx.simulate_shared_keystrokes("o").await;
+        cx.shared_state().await.assert_eq("// hello\n// ห‡\n");
+        cx.simulate_shared_keystrokes("x escape shift-o").await;
+        cx.shared_state().await.assert_eq("// hello\n// ห‡\n// x\n");
+    }
 }

crates/vim/test_data/test_o_comment.json ๐Ÿ”—

@@ -0,0 +1,8 @@
+{"SetOption":{"value":"filetype=rust"}}
+{"Put":{"state":"// helloห‡\n"}}
+{"Key":"o"}
+{"Get":{"state":"// hello\n// ห‡\n","mode":"Insert"}}
+{"Key":"x"}
+{"Key":"escape"}
+{"Key":"shift-o"}
+{"Get":{"state":"// hello\n// ห‡\n// x\n","mode":"Insert"}}