From 09ed149184356e7e88ab7d4a9117f7511c7af20d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 20 Jul 2022 09:07:54 -0700 Subject: [PATCH] Improve calculation of which lines are new when auto-indenting --- crates/language/src/buffer.rs | 92 +++++++++++++++++++---------------- crates/language/src/tests.rs | 73 +++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 44 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index bff9438124..e2685ba36e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -232,7 +232,7 @@ struct SyntaxTree { struct AutoindentRequest { before_edit: BufferSnapshot, edited: Vec, - inserted: Option>>, + inserted: Vec>, indent_size: IndentSize, } @@ -874,9 +874,10 @@ impl Buffer { yield_now().await; } - if let Some(inserted) = request.inserted.as_ref() { + if !request.inserted.is_empty() { let inserted_row_ranges = contiguous_ranges( - inserted + request + .inserted .iter() .map(|range| range.to_point(&snapshot)) .flat_map(|range| range.start.row..range.end.row + 1), @@ -1203,52 +1204,61 @@ impl Buffer { self.start_transaction(); self.pending_autoindent.take(); - let autoindent_request = - self.language - .as_ref() - .and_then(|_| autoindent_size) - .map(|autoindent_size| { - let before_edit = self.snapshot(); - let edited = edits - .iter() - .filter_map(|(range, new_text)| { - let start = range.start.to_point(self); - if new_text.starts_with('\n') - && start.column == self.line_len(start.row) - { - None - } else { - Some(self.anchor_before(range.start)) - } - }) - .collect(); - (before_edit, edited, autoindent_size) - }); + let autoindent_request = self + .language + .as_ref() + .and_then(|_| autoindent_size) + .map(|autoindent_size| (self.snapshot(), autoindent_size)); let edit_operation = self.text.edit(edits.iter().cloned()); let edit_id = edit_operation.local_timestamp(); - if let Some((before_edit, edited, size)) = autoindent_request { - let mut delta = 0isize; + if let Some((before_edit, size)) = autoindent_request { + let mut inserted = Vec::new(); + let mut edited = Vec::new(); - let inserted_ranges = edits + let mut delta = 0isize; + for ((range, _), new_text) in edits .into_iter() .zip(&edit_operation.as_edit().unwrap().new_text) - .filter_map(|((range, _), new_text)| { - let first_newline_ix = new_text.find('\n')?; - let new_text_len = new_text.len(); - let start = (delta + range.start as isize) as usize + first_newline_ix + 1; - let end = (delta + range.start as isize) as usize + new_text_len; - delta += new_text_len as isize - (range.end as isize - range.start as isize); - Some(self.anchor_before(start)..self.anchor_after(end)) - }) - .collect::>>(); + { + let new_text_len = new_text.len(); + let first_newline_ix = new_text.find('\n'); + let old_start = range.start.to_point(&before_edit); - let inserted = if inserted_ranges.is_empty() { - None - } else { - Some(inserted_ranges) - }; + let start = (delta + range.start as isize) as usize; + delta += new_text_len as isize - (range.end as isize - range.start as isize); + + // When inserting multiple lines of text at the beginning of a line, + // treat all of the affected lines as newly-inserted. + if first_newline_ix.is_some() + && old_start.column < before_edit.indent_size_for_line(old_start.row).len + { + inserted + .push(self.anchor_before(start)..self.anchor_after(start + new_text_len)); + continue; + } + + // When inserting a newline at the end of an existing line, treat the following + // line as newly-inserted. + if first_newline_ix == Some(0) + && old_start.column == before_edit.line_len(old_start.row) + { + inserted.push( + self.anchor_before(start + 1)..self.anchor_after(start + new_text_len), + ); + continue; + } + + // Otherwise, mark the start of the edit as edited, and any subsequent + // lines as newly inserted. + edited.push(before_edit.anchor_before(range.start)); + if let Some(ix) = first_newline_ix { + inserted.push( + self.anchor_before(start + ix + 1)..self.anchor_after(start + new_text_len), + ); + } + } self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index ac3759c257..3f1f0f1b8c 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -756,8 +756,18 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta }); cx.add_model(|cx| { - let text = "fn a() {\n {\n b()?\n }\n\n Ok(())\n}"; + let text = " + fn a() { + { + b()? + } + Ok(()) + } + " + .unindent(); let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + + // Delete a closing curly brace changes the suggested indent for the line. buffer.edit_with_autoindent( [(Point::new(3, 4)..Point::new(3, 5), "")], IndentSize::spaces(4), @@ -765,9 +775,19 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta ); assert_eq!( buffer.text(), - "fn a() {\n {\n b()?\n \n\n Ok(())\n}" + " + fn a() { + { + b()? + | + Ok(()) + } + " + .replace("|", "") // included in the string to preserve trailing whites + .unindent() ); + // Manually editing the leading whitespace buffer.edit_with_autoindent( [(Point::new(3, 0)..Point::new(3, 12), "")], IndentSize::spaces(4), @@ -775,7 +795,15 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta ); assert_eq!( buffer.text(), - "fn a() {\n {\n b()?\n\n\n Ok(())\n}" + " + fn a() { + { + b()? + + Ok(()) + } + " + .unindent() ); buffer }); @@ -832,6 +860,45 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) { }); } +#[gpui::test] +fn test_autoindent_multi_line_insertion(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = " + const a: usize = 1; + fn b() { + if c { + let d = 2; + } + } + " + .unindent(); + + let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); + buffer.edit_with_autoindent( + [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")], + IndentSize::spaces(4), + cx, + ); + assert_eq!( + buffer.text(), + " + const a: usize = 1; + fn b() { + if c { + e( + f() + ); + let d = 2; + } + } + " + .unindent() + ); + + buffer + }); +} + #[gpui::test] fn test_autoindent_disabled(cx: &mut MutableAppContext) { cx.add_model(|cx| {