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 {
before_edit: BufferSnapshot,
edited: Vec<Anchor>,
inserted: Option<Vec<Range<Anchor>>>,
inserted: Vec<Range<Anchor>>,
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::<Vec<Range<Anchor>>>();
{
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,

View file

@ -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| {