Merge pull request #2035 from zed-industries/always-auto-indent-block-on-paste

Max Brunsfeld created

Always auto-indent in block-wise mode when pasting

Change summary

crates/editor/src/editor.rs         |  4 
crates/language/src/buffer.rs       | 30 +++++----
crates/language/src/buffer_tests.rs | 95 +++++++++++++++++++++++++++++-
3 files changed, 108 insertions(+), 21 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -2020,7 +2020,9 @@ impl Editor {
                     old_selections
                         .iter()
                         .map(|s| (s.start..s.end, text.clone())),
-                    Some(AutoindentMode::EachLine),
+                    Some(AutoindentMode::Block {
+                        original_indent_columns: Vec::new(),
+                    }),
                     cx,
                 );
                 anchors

crates/language/src/buffer.rs 🔗

@@ -1349,13 +1349,6 @@ impl Buffer {
         let edit_id = edit_operation.local_timestamp();
 
         if let Some((before_edit, mode)) = autoindent_request {
-            let (start_columns, is_block_mode) = match mode {
-                AutoindentMode::Block {
-                    original_indent_columns: start_columns,
-                } => (start_columns, true),
-                AutoindentMode::EachLine => (Default::default(), false),
-            };
-
             let mut delta = 0isize;
             let entries = edits
                 .into_iter()
@@ -1369,7 +1362,7 @@ impl Buffer {
 
                     let mut range_of_insertion_to_indent = 0..new_text_len;
                     let mut first_line_is_new = false;
-                    let mut start_column = None;
+                    let mut original_indent_column = None;
 
                     // When inserting an entire line at the beginning of an existing line,
                     // treat the insertion as new.
@@ -1381,14 +1374,23 @@ impl Buffer {
 
                     // When inserting text starting with a newline, avoid auto-indenting the
                     // previous line.
-                    if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') {
+                    if new_text.starts_with('\n') {
                         range_of_insertion_to_indent.start += 1;
                         first_line_is_new = true;
                     }
 
                     // Avoid auto-indenting after the insertion.
-                    if is_block_mode {
-                        start_column = start_columns.get(ix).copied();
+                    if let AutoindentMode::Block {
+                        original_indent_columns,
+                    } = &mode
+                    {
+                        original_indent_column =
+                            Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| {
+                                indent_size_for_text(
+                                    new_text[range_of_insertion_to_indent.clone()].chars(),
+                                )
+                                .len
+                            }));
                         if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
                             range_of_insertion_to_indent.end -= 1;
                         }
@@ -1396,7 +1398,7 @@ impl Buffer {
 
                     AutoindentRequestEntry {
                         first_line_is_new,
-                        original_indent_column: start_column,
+                        original_indent_column,
                         indent_size: before_edit.language_indent_size_at(range.start, cx),
                         range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
                             ..self.anchor_after(new_start + range_of_insertion_to_indent.end),
@@ -1407,7 +1409,7 @@ impl Buffer {
             self.autoindent_requests.push(Arc::new(AutoindentRequest {
                 before_edit,
                 entries,
-                is_block_mode,
+                is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
             }));
         }
 
@@ -2414,7 +2416,7 @@ impl BufferSnapshot {
     }
 }
 
-pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
+fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
     indent_size_for_text(text.chars_at(Point::new(row, 0)))
 }
 

crates/language/src/buffer_tests.rs 🔗

@@ -995,12 +995,17 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
         .unindent();
         let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
 
+        // When this text was copied, both of the quotation marks were at the same
+        // indent level, but the indentation of the first line was not included in
+        // the copied text. This information is retained in the
+        // 'original_indent_columns' vector.
+        let original_indent_columns = vec![4];
         let inserted_text = r#"
             "
-              c
-                d
-                  e
-            "
+                  c
+                    d
+                      e
+                "
         "#
         .unindent();
 
@@ -1009,7 +1014,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
         buffer.edit(
             [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
             Some(AutoindentMode::Block {
-                original_indent_columns: vec![0],
+                original_indent_columns: original_indent_columns.clone(),
             }),
             cx,
         );
@@ -1037,7 +1042,7 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
         buffer.edit(
             [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
             Some(AutoindentMode::Block {
-                original_indent_columns: vec![0],
+                original_indent_columns: original_indent_columns.clone(),
             }),
             cx,
         );
@@ -1060,6 +1065,84 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
     });
 }
 
+#[gpui::test]
+fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut MutableAppContext) {
+    cx.set_global(Settings::test(cx));
+    cx.add_model(|cx| {
+        let text = r#"
+            fn a() {
+                if b() {
+
+                }
+            }
+        "#
+        .unindent();
+        let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
+
+        // The original indent columns are not known, so this text is
+        // auto-indented in a block as if the first line was copied in
+        // its entirety.
+        let original_indent_columns = Vec::new();
+        let inserted_text = "    c\n        .d()\n        .e();";
+
+        // Insert the block at column zero. The entire block is indented
+        // so that the first line matches the previous line's indentation.
+        buffer.edit(
+            [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
+            Some(AutoindentMode::Block {
+                original_indent_columns: original_indent_columns.clone(),
+            }),
+            cx,
+        );
+        assert_eq!(
+            buffer.text(),
+            r#"
+            fn a() {
+                if b() {
+                    c
+                        .d()
+                        .e();
+                }
+            }
+            "#
+            .unindent()
+        );
+
+        // Grouping is disabled in tests, so we need 2 undos
+        buffer.undo(cx); // Undo the auto-indent
+        buffer.undo(cx); // Undo the original edit
+
+        // Insert the block at a deeper indent level. The entire block is outdented.
+        buffer.edit(
+            [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
+            None,
+            cx,
+        );
+        buffer.edit(
+            [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
+            Some(AutoindentMode::Block {
+                original_indent_columns: Vec::new(),
+            }),
+            cx,
+        );
+        assert_eq!(
+            buffer.text(),
+            r#"
+            fn a() {
+                if b() {
+                    c
+                        .d()
+                        .e();
+                }
+            }
+            "#
+            .unindent()
+        );
+
+        buffer
+    });
+}
+
 #[gpui::test]
 fn test_autoindent_language_without_indents_query(cx: &mut MutableAppContext) {
     cx.set_global(Settings::test(cx));