From 244f2593314e6c83c2c890af6c5351fea0ec037b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 17:42:00 -0800 Subject: [PATCH 1/2] Always auto-indent in block-wise mode when pasting If the text was copied outside of Zed, so the original indent column is unknown, then act as if the first line was copied in its entirety. --- crates/editor/src/editor.rs | 4 +++- crates/language/src/buffer.rs | 30 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8847c01af7..fb8e37077e 100644 --- a/crates/editor/src/editor.rs +++ b/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 diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 9ceb6b32a9..d1e7316f1b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1332,13 +1332,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() @@ -1352,7 +1345,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. @@ -1364,14 +1357,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; } @@ -1379,7 +1381,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), @@ -1390,7 +1392,7 @@ impl Buffer { self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, entries, - is_block_mode, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), })); } @@ -2397,7 +2399,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))) } From 0bd6f9b6ceb09d639e8bb9aec886f5acb2008305 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Jan 2023 18:06:58 -0800 Subject: [PATCH 2/2] Add a test for block-wise auto-indent without original indent info --- crates/language/src/buffer_tests.rs | 95 +++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 09ccc5d621..b7f35e9ad5 100644 --- a/crates/language/src/buffer_tests.rs +++ b/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));