Start work on adjusting pasted text based on old start column
This commit is contained in:
parent
3a74290359
commit
2d05f906f1
2 changed files with 84 additions and 43 deletions
|
@ -252,6 +252,8 @@ struct AutoindentRequestEntry {
|
||||||
/// only be adjusted if the suggested indentation level has *changed*
|
/// only be adjusted if the suggested indentation level has *changed*
|
||||||
/// since the edit was made.
|
/// since the edit was made.
|
||||||
first_line_is_new: bool,
|
first_line_is_new: bool,
|
||||||
|
/// The original indentation of the text that was inserted into this range.
|
||||||
|
original_indent: Option<IndentSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -814,6 +816,8 @@ impl Buffer {
|
||||||
Some(async move {
|
Some(async move {
|
||||||
let mut indent_sizes = BTreeMap::new();
|
let mut indent_sizes = BTreeMap::new();
|
||||||
for request in autoindent_requests {
|
for request in autoindent_requests {
|
||||||
|
// Resolve each edited range to its row in the current buffer and in the
|
||||||
|
// buffer before this batch of edits.
|
||||||
let mut row_ranges = Vec::new();
|
let mut row_ranges = Vec::new();
|
||||||
let mut old_to_new_rows = BTreeMap::new();
|
let mut old_to_new_rows = BTreeMap::new();
|
||||||
for entry in &request.entries {
|
for entry in &request.entries {
|
||||||
|
@ -824,11 +828,12 @@ impl Buffer {
|
||||||
let old_row = position.to_point(&request.before_edit).row;
|
let old_row = position.to_point(&request.before_edit).row;
|
||||||
old_to_new_rows.insert(old_row, new_row);
|
old_to_new_rows.insert(old_row, new_row);
|
||||||
}
|
}
|
||||||
if new_end_row > new_row {
|
row_ranges.push((new_row..new_end_row, entry.original_indent));
|
||||||
row_ranges.push(new_row..new_end_row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a map containing the suggested indentation for each of the edited lines
|
||||||
|
// with respect to the state of the buffer before these edits. This map is keyed
|
||||||
|
// by the rows for these lines in the current state of the buffer.
|
||||||
let mut old_suggestions = BTreeMap::<u32, IndentSize>::default();
|
let mut old_suggestions = BTreeMap::<u32, IndentSize>::default();
|
||||||
let old_edited_ranges =
|
let old_edited_ranges =
|
||||||
contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
|
contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
|
||||||
|
@ -856,17 +861,18 @@ impl Buffer {
|
||||||
yield_now().await;
|
yield_now().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, old_suggestions contains the suggested indentation for all edited lines
|
// In block mode, only compute indentation suggestions for the first line
|
||||||
// with respect to the state of the buffer before the edit, but keyed by the row for these
|
// of each insertion. Otherwise, compute suggestions for every inserted line.
|
||||||
// lines after the edits were applied.
|
|
||||||
|
|
||||||
let new_edited_row_ranges = contiguous_ranges(
|
let new_edited_row_ranges = contiguous_ranges(
|
||||||
row_ranges.iter().flat_map(|range| match request.mode {
|
row_ranges.iter().flat_map(|(range, _)| match request.mode {
|
||||||
AutoindentMode::Block => range.start..range.start + 1,
|
AutoindentMode::Block => range.start..range.start + 1,
|
||||||
AutoindentMode::Independent => range.clone(),
|
AutoindentMode::Independent => range.clone(),
|
||||||
}),
|
}),
|
||||||
max_rows_between_yields,
|
max_rows_between_yields,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Compute new suggestions for each line, but only include them in the result
|
||||||
|
// if they differ from the old suggestion for that line.
|
||||||
for new_edited_row_range in new_edited_row_ranges {
|
for new_edited_row_range in new_edited_row_ranges {
|
||||||
let suggestions = snapshot
|
let suggestions = snapshot
|
||||||
.suggest_autoindents(new_edited_row_range.clone())
|
.suggest_autoindents(new_edited_row_range.clone())
|
||||||
|
@ -894,27 +900,34 @@ impl Buffer {
|
||||||
yield_now().await;
|
yield_now().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For each block of inserted text, adjust the indentation of the remaining
|
||||||
|
// lines of the block by the same amount as the first line was adjusted.
|
||||||
if matches!(request.mode, AutoindentMode::Block) {
|
if matches!(request.mode, AutoindentMode::Block) {
|
||||||
for row_range in row_ranges {
|
for (row_range, original_indent) in
|
||||||
if row_range.len() > 1 {
|
row_ranges
|
||||||
if let Some(new_indent_size) =
|
.into_iter()
|
||||||
indent_sizes.get(&row_range.start).copied()
|
.filter_map(|(range, original_indent)| {
|
||||||
|
if range.len() > 1 {
|
||||||
|
Some((range, original_indent?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
{
|
{
|
||||||
let old_indent_size =
|
let new_indent = indent_sizes
|
||||||
snapshot.indent_size_for_line(row_range.start);
|
.get(&row_range.start)
|
||||||
if new_indent_size.kind == old_indent_size.kind {
|
.copied()
|
||||||
let delta =
|
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
|
||||||
new_indent_size.len as i64 - old_indent_size.len as i64;
|
let delta = new_indent.len as i64 - original_indent.len as i64;
|
||||||
if delta != 0 {
|
if new_indent.kind == original_indent.kind && delta != 0 {
|
||||||
for row in row_range.skip(1) {
|
for row in row_range.skip(1) {
|
||||||
indent_sizes.entry(row).or_insert_with(|| {
|
indent_sizes.entry(row).or_insert_with(|| {
|
||||||
let mut size = snapshot.indent_size_for_line(row);
|
let mut size = snapshot.indent_size_for_line(row);
|
||||||
if size.kind == new_indent_size.kind {
|
if size.kind == new_indent.kind {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
size.len += delta as u32;
|
size.len = size.len + delta as u32;
|
||||||
} else if delta < 0 {
|
} else if delta < 0 {
|
||||||
size.len =
|
size.len = size.len.saturating_sub(-delta as u32);
|
||||||
size.len.saturating_sub(-delta as u32);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size
|
size
|
||||||
|
@ -924,9 +937,6 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
indent_sizes
|
indent_sizes
|
||||||
})
|
})
|
||||||
|
@ -1226,6 +1236,7 @@ impl Buffer {
|
||||||
|
|
||||||
let mut range_of_insertion_to_indent = 0..new_text_len;
|
let mut range_of_insertion_to_indent = 0..new_text_len;
|
||||||
let mut first_line_is_new = false;
|
let mut first_line_is_new = false;
|
||||||
|
let mut original_indent = None;
|
||||||
|
|
||||||
// When inserting an entire line at the beginning of an existing line,
|
// When inserting an entire line at the beginning of an existing line,
|
||||||
// treat the insertion as new.
|
// treat the insertion as new.
|
||||||
|
@ -1235,13 +1246,16 @@ impl Buffer {
|
||||||
first_line_is_new = true;
|
first_line_is_new = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid auto-indenting lines before and after the insertion.
|
// 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[range_of_insertion_to_indent.clone()].starts_with('\n') {
|
||||||
range_of_insertion_to_indent.start += 1;
|
range_of_insertion_to_indent.start += 1;
|
||||||
first_line_is_new = true;
|
first_line_is_new = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid auto-indenting before the insertion.
|
||||||
if matches!(mode, AutoindentMode::Block) {
|
if matches!(mode, AutoindentMode::Block) {
|
||||||
|
original_indent = Some(indent_size_for_text(new_text.chars()));
|
||||||
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
|
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
|
||||||
range_of_insertion_to_indent.end -= 1;
|
range_of_insertion_to_indent.end -= 1;
|
||||||
}
|
}
|
||||||
|
@ -1249,6 +1263,7 @@ impl Buffer {
|
||||||
|
|
||||||
AutoindentRequestEntry {
|
AutoindentRequestEntry {
|
||||||
first_line_is_new,
|
first_line_is_new,
|
||||||
|
original_indent,
|
||||||
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
|
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
|
||||||
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
|
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
|
||||||
}
|
}
|
||||||
|
@ -2144,8 +2159,12 @@ impl BufferSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
|
pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
|
||||||
|
indent_size_for_text(text.chars_at(Point::new(row, 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indent_size_for_text(text: impl Iterator<Item = char>) -> IndentSize {
|
||||||
let mut result = IndentSize::spaces(0);
|
let mut result = IndentSize::spaces(0);
|
||||||
for c in text.chars_at(Point::new(row, 0)) {
|
for c in text {
|
||||||
let kind = match c {
|
let kind = match c {
|
||||||
' ' => IndentKind::Space,
|
' ' => IndentKind::Space,
|
||||||
'\t' => IndentKind::Tab,
|
'\t' => IndentKind::Tab,
|
||||||
|
|
|
@ -920,20 +920,18 @@ fn test_autoindent_multi_line_insertion(cx: &mut MutableAppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_autoindent_preserves_relative_indentation_in_multi_line_insertion(
|
fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
|
||||||
cx: &mut MutableAppContext,
|
|
||||||
) {
|
|
||||||
cx.set_global(Settings::test(cx));
|
cx.set_global(Settings::test(cx));
|
||||||
cx.add_model(|cx| {
|
cx.add_model(|cx| {
|
||||||
let text = "
|
let text = r#"
|
||||||
fn a() {
|
fn a() {
|
||||||
b();
|
b();
|
||||||
}
|
}
|
||||||
"
|
"#
|
||||||
.unindent();
|
.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);
|
||||||
|
|
||||||
let pasted_text = r#"
|
let inserted_text = r#"
|
||||||
"
|
"
|
||||||
c
|
c
|
||||||
d
|
d
|
||||||
|
@ -942,9 +940,33 @@ fn test_autoindent_preserves_relative_indentation_in_multi_line_insertion(
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
// insert at the beginning of a line
|
// Insert the block at column zero. The entire block is indented
|
||||||
|
// so that the first line matches the previous line's indentation.
|
||||||
buffer.edit(
|
buffer.edit(
|
||||||
[(Point::new(2, 0)..Point::new(2, 0), pasted_text.clone())],
|
[(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
|
||||||
|
Some(AutoindentMode::Block),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
buffer.text(),
|
||||||
|
r#"
|
||||||
|
fn a() {
|
||||||
|
b();
|
||||||
|
"
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Insert the block at a deeper indent level. The entire block is outdented.
|
||||||
|
buffer.undo(cx);
|
||||||
|
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
|
||||||
|
buffer.edit(
|
||||||
|
[(Point::new(2, 8)..Point::new(2, 8), inserted_text.clone())],
|
||||||
Some(AutoindentMode::Block),
|
Some(AutoindentMode::Block),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue