Improve calculation of which lines are new when auto-indenting

This commit is contained in:
Max Brunsfeld 2022-07-20 09:07:54 -07:00
parent 6b9c1e78c1
commit 09ed149184
2 changed files with 121 additions and 44 deletions

View file

@ -232,7 +232,7 @@ struct SyntaxTree {
struct AutoindentRequest { struct AutoindentRequest {
before_edit: BufferSnapshot, before_edit: BufferSnapshot,
edited: Vec<Anchor>, edited: Vec<Anchor>,
inserted: Option<Vec<Range<Anchor>>>, inserted: Vec<Range<Anchor>>,
indent_size: IndentSize, indent_size: IndentSize,
} }
@ -874,9 +874,10 @@ impl Buffer {
yield_now().await; yield_now().await;
} }
if let Some(inserted) = request.inserted.as_ref() { if !request.inserted.is_empty() {
let inserted_row_ranges = contiguous_ranges( let inserted_row_ranges = contiguous_ranges(
inserted request
.inserted
.iter() .iter()
.map(|range| range.to_point(&snapshot)) .map(|range| range.to_point(&snapshot))
.flat_map(|range| range.start.row..range.end.row + 1), .flat_map(|range| range.start.row..range.end.row + 1),
@ -1203,52 +1204,61 @@ impl Buffer {
self.start_transaction(); self.start_transaction();
self.pending_autoindent.take(); self.pending_autoindent.take();
let autoindent_request = let autoindent_request = self
self.language .language
.as_ref() .as_ref()
.and_then(|_| autoindent_size) .and_then(|_| autoindent_size)
.map(|autoindent_size| { .map(|autoindent_size| (self.snapshot(), 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 edit_operation = self.text.edit(edits.iter().cloned()); let edit_operation = self.text.edit(edits.iter().cloned());
let edit_id = edit_operation.local_timestamp(); let edit_id = edit_operation.local_timestamp();
if let Some((before_edit, edited, size)) = autoindent_request { if let Some((before_edit, size)) = autoindent_request {
let mut delta = 0isize; 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() .into_iter()
.zip(&edit_operation.as_edit().unwrap().new_text) .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 new_text_len = new_text.len(); let first_newline_ix = new_text.find('\n');
let start = (delta + range.start as isize) as usize + first_newline_ix + 1; let old_start = range.start.to_point(&before_edit);
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::<Vec<Range<Anchor>>>();
let inserted = if inserted_ranges.is_empty() { let start = (delta + range.start as isize) as usize;
None delta += new_text_len as isize - (range.end as isize - range.start as isize);
} else {
Some(inserted_ranges) // 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 { self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit, before_edit,

View file

@ -756,8 +756,18 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
}); });
cx.add_model(|cx| { 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); 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( buffer.edit_with_autoindent(
[(Point::new(3, 4)..Point::new(3, 5), "")], [(Point::new(3, 4)..Point::new(3, 5), "")],
IndentSize::spaces(4), IndentSize::spaces(4),
@ -765,9 +775,19 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
); );
assert_eq!( assert_eq!(
buffer.text(), 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( buffer.edit_with_autoindent(
[(Point::new(3, 0)..Point::new(3, 12), "")], [(Point::new(3, 0)..Point::new(3, 12), "")],
IndentSize::spaces(4), IndentSize::spaces(4),
@ -775,7 +795,15 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
); );
assert_eq!( assert_eq!(
buffer.text(), buffer.text(),
"fn a() {\n {\n b()?\n\n\n Ok(())\n}" "
fn a() {
{
b()?
Ok(())
}
"
.unindent()
); );
buffer 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] #[gpui::test]
fn test_autoindent_disabled(cx: &mut MutableAppContext) { fn test_autoindent_disabled(cx: &mut MutableAppContext) {
cx.add_model(|cx| { cx.add_model(|cx| {