Detailed changes
@@ -879,6 +879,7 @@ struct ActiveDiagnosticGroup {
pub struct ClipboardSelection {
pub len: usize,
pub is_entire_line: bool,
+ pub first_line_indent: u32,
}
#[derive(Debug)]
@@ -1926,7 +1927,7 @@ impl Editor {
old_selections
.iter()
.map(|s| (s.start..s.end, text.clone())),
- Some(AutoindentMode::Block),
+ Some(AutoindentMode::Independent),
cx,
);
anchors
@@ -2368,7 +2369,7 @@ impl Editor {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(
ranges.iter().map(|range| (range.clone(), text)),
- Some(AutoindentMode::Block),
+ Some(AutoindentMode::Independent),
cx,
);
});
@@ -3512,6 +3513,10 @@ impl Editor {
clipboard_selections.push(ClipboardSelection {
len,
is_entire_line,
+ first_line_indent: cmp::min(
+ selection.start.column,
+ buffer.indent_size_for_line(selection.start.row).len,
+ ),
});
}
}
@@ -3549,6 +3554,10 @@ impl Editor {
clipboard_selections.push(ClipboardSelection {
len,
is_entire_line,
+ first_line_indent: cmp::min(
+ start.column,
+ buffer.indent_size_for_line(start.row).len,
+ ),
});
}
}
@@ -3583,18 +3592,22 @@ impl Editor {
let snapshot = buffer.read(cx);
let mut start_offset = 0;
let mut edits = Vec::new();
+ let mut start_columns = Vec::new();
let line_mode = this.selections.line_mode;
for (ix, selection) in old_selections.iter().enumerate() {
let to_insert;
let entire_line;
+ let start_column;
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
let end_offset = start_offset + clipboard_selection.len;
to_insert = &clipboard_text[start_offset..end_offset];
entire_line = clipboard_selection.is_entire_line;
start_offset = end_offset;
+ start_column = clipboard_selection.first_line_indent;
} else {
to_insert = clipboard_text.as_str();
entire_line = all_selections_were_entire_line;
+ start_column = 0;
}
// If the corresponding selection was empty when this slice of the
@@ -3610,9 +3623,10 @@ impl Editor {
};
edits.push((range, to_insert));
+ start_columns.push(start_column);
}
drop(snapshot);
- buffer.edit(edits, Some(AutoindentMode::Block), cx);
+ buffer.edit(edits, Some(AutoindentMode::Block { start_columns }), cx);
});
let selections = this.selections.all::<usize>(cx);
@@ -8649,6 +8663,118 @@ mod tests {
t|he lazy dog"});
}
+ #[gpui::test]
+ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
+ let mut cx = EditorTestContext::new(cx).await;
+ let language = Arc::new(Language::new(
+ LanguageConfig::default(),
+ Some(tree_sitter_rust::language()),
+ ));
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+ // Cut an indented block, without the leading whitespace.
+ cx.set_state(indoc! {"
+ const a = (
+ b(),
+ [c(
+ d,
+ e
+ )}
+ );
+ "});
+ cx.update_editor(|e, cx| e.cut(&Cut, cx));
+ cx.assert_editor_state(indoc! {"
+ const a = (
+ b(),
+ |
+ );
+ "});
+
+ // Paste it at the same position.
+ cx.update_editor(|e, cx| e.paste(&Paste, cx));
+ cx.assert_editor_state(indoc! {"
+ const a = (
+ b(),
+ c(
+ d,
+ e
+ )|
+ );
+ "});
+
+ // Paste it at a line with a lower indent level.
+ cx.update_editor(|e, cx| e.paste(&Paste, cx));
+ cx.set_state(indoc! {"
+ |
+ const a = (
+ b(),
+ );
+ "});
+ cx.update_editor(|e, cx| e.paste(&Paste, cx));
+ cx.assert_editor_state(indoc! {"
+ c(
+ d,
+ e
+ )|
+ const a = (
+ b(),
+ );
+ "});
+
+ // Cut an indented block, with the leading whitespace.
+ cx.set_state(indoc! {"
+ const a = (
+ b(),
+ [ c(
+ d,
+ e
+ )
+ });
+ "});
+ cx.update_editor(|e, cx| e.cut(&Cut, cx));
+ cx.assert_editor_state(indoc! {"
+ const a = (
+ b(),
+ |);
+ "});
+
+ // Paste it at the same position.
+ cx.update_editor(|e, cx| e.paste(&Paste, cx));
+ cx.assert_editor_state(indoc! {"
+ const a = (
+ b(),
+ c(
+ d,
+ e
+ )
+ |);
+ "});
+
+ // Paste it at a line with a higher indent level.
+ cx.set_state(indoc! {"
+ const a = (
+ b(),
+ c(
+ d,
+ e|
+ )
+ );
+ "});
+ cx.update_editor(|e, cx| e.paste(&Paste, cx));
+ cx.set_state(indoc! {"
+ const a = (
+ b(),
+ c(
+ d,
+ ec(
+ d,
+ e
+ )|
+ )
+ );
+ "});
+ }
+
#[gpui::test]
fn test_select_all(cx: &mut gpui::MutableAppContext) {
cx.set_global(Settings::test(cx));
@@ -305,7 +305,7 @@ impl MultiBuffer {
pub fn edit<I, S, T>(
&mut self,
edits: I,
- autoindent_mode: Option<AutoindentMode>,
+ mut autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Self>,
) where
I: IntoIterator<Item = (Range<S>, T)>,
@@ -331,11 +331,17 @@ impl MultiBuffer {
});
}
- let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, Arc<str>, bool)>> =
+ let indent_start_columns = match &mut autoindent_mode {
+ Some(AutoindentMode::Block { start_columns }) => mem::take(start_columns),
+ _ => Default::default(),
+ };
+
+ let mut buffer_edits: HashMap<usize, Vec<(Range<usize>, Arc<str>, bool, u32)>> =
Default::default();
let mut cursor = snapshot.excerpts.cursor::<usize>();
- for (range, new_text) in edits {
+ for (ix, (range, new_text)) in edits.enumerate() {
let new_text: Arc<str> = new_text.into();
+ let start_column = indent_start_columns.get(ix).copied().unwrap_or(0);
cursor.seek(&range.start, Bias::Right, &());
if cursor.item().is_none() && range.start == *cursor.start() {
cursor.prev(&());
@@ -366,7 +372,7 @@ impl MultiBuffer {
buffer_edits
.entry(start_excerpt.buffer_id)
.or_insert(Vec::new())
- .push((buffer_start..buffer_end, new_text, true));
+ .push((buffer_start..buffer_end, new_text, true, start_column));
} else {
let start_excerpt_range = buffer_start
..start_excerpt
@@ -383,11 +389,11 @@ impl MultiBuffer {
buffer_edits
.entry(start_excerpt.buffer_id)
.or_insert(Vec::new())
- .push((start_excerpt_range, new_text.clone(), true));
+ .push((start_excerpt_range, new_text.clone(), true, start_column));
buffer_edits
.entry(end_excerpt.buffer_id)
.or_insert(Vec::new())
- .push((end_excerpt_range, new_text.clone(), false));
+ .push((end_excerpt_range, new_text.clone(), false, start_column));
cursor.seek(&range.start, Bias::Right, &());
cursor.next(&());
@@ -402,6 +408,7 @@ impl MultiBuffer {
excerpt.range.context.to_offset(&excerpt.buffer),
new_text.clone(),
false,
+ start_column,
));
cursor.next(&());
}
@@ -409,19 +416,21 @@ impl MultiBuffer {
}
for (buffer_id, mut edits) in buffer_edits {
- edits.sort_unstable_by_key(|(range, _, _)| range.start);
+ edits.sort_unstable_by_key(|(range, _, _, _)| range.start);
self.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
+ let mut insertion_start_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = "".into();
- while let Some((mut range, new_text, mut is_insertion)) = edits.next() {
- while let Some((next_range, _, next_is_insertion)) = edits.peek() {
+ while let Some((mut range, new_text, mut is_insertion, start_column)) =
+ edits.next()
+ {
+ while let Some((next_range, _, next_is_insertion, _)) = edits.peek() {
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
-
is_insertion |= *next_is_insertion;
edits.next();
} else {
@@ -430,6 +439,7 @@ impl MultiBuffer {
}
if is_insertion {
+ insertion_start_columns.push(start_column);
insertions.push((
buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
new_text.clone(),
@@ -442,8 +452,25 @@ impl MultiBuffer {
}
}
- buffer.edit(deletions, autoindent_mode, cx);
- buffer.edit(insertions, autoindent_mode, cx);
+ let deletion_autoindent_mode =
+ if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+ Some(AutoindentMode::Block {
+ start_columns: Default::default(),
+ })
+ } else {
+ None
+ };
+ let insertion_autoindent_mode =
+ if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+ Some(AutoindentMode::Block {
+ start_columns: insertion_start_columns,
+ })
+ } else {
+ None
+ };
+
+ buffer.edit(deletions, deletion_autoindent_mode, cx);
+ buffer.edit(insertions, insertion_autoindent_mode, cx);
})
}
}
@@ -229,9 +229,9 @@ struct SyntaxTree {
version: clock::Global,
}
-#[derive(Clone, Copy)]
+#[derive(Clone, Debug)]
pub enum AutoindentMode {
- Block,
+ Block { start_columns: Vec<u32> },
Independent,
}
@@ -240,7 +240,7 @@ struct AutoindentRequest {
before_edit: BufferSnapshot,
entries: Vec<AutoindentRequestEntry>,
indent_size: IndentSize,
- mode: AutoindentMode,
+ is_block_mode: bool,
}
#[derive(Clone)]
@@ -252,8 +252,7 @@ struct AutoindentRequestEntry {
/// only be adjusted if the suggested indentation level has *changed*
/// since the edit was made.
first_line_is_new: bool,
- /// The original indentation of the text that was inserted into this range.
- original_indent: Option<IndentSize>,
+ start_column: Option<u32>,
}
#[derive(Debug)]
@@ -828,7 +827,7 @@ impl Buffer {
let old_row = position.to_point(&request.before_edit).row;
old_to_new_rows.insert(old_row, new_row);
}
- row_ranges.push((new_row..new_end_row, entry.original_indent));
+ row_ranges.push((new_row..new_end_row, entry.start_column));
}
// Build a map containing the suggested indentation for each of the edited lines
@@ -864,9 +863,12 @@ impl Buffer {
// In block mode, only compute indentation suggestions for the first line
// of each insertion. Otherwise, compute suggestions for every inserted line.
let new_edited_row_ranges = contiguous_ranges(
- row_ranges.iter().flat_map(|(range, _)| match request.mode {
- AutoindentMode::Block => range.start..range.start + 1,
- AutoindentMode::Independent => range.clone(),
+ row_ranges.iter().flat_map(|(range, _)| {
+ if request.is_block_mode {
+ range.start..range.start + 1
+ } else {
+ range.clone()
+ }
}),
max_rows_between_yields,
);
@@ -902,24 +904,22 @@ impl Buffer {
// 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) {
- for (row_range, original_indent) in
- row_ranges
- .into_iter()
- .filter_map(|(range, original_indent)| {
- if range.len() > 1 {
- Some((range, original_indent?))
- } else {
- None
- }
- })
+ if request.is_block_mode {
+ for (row_range, start_column) in
+ row_ranges.into_iter().filter_map(|(range, start_column)| {
+ if range.len() > 1 {
+ Some((range, start_column?))
+ } else {
+ None
+ }
+ })
{
let new_indent = indent_sizes
.get(&row_range.start)
.copied()
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
- let delta = new_indent.len as i64 - original_indent.len as i64;
- if new_indent.kind == original_indent.kind && delta != 0 {
+ let delta = new_indent.len as i64 - start_column as i64;
+ if delta != 0 {
for row in row_range.skip(1) {
indent_sizes.entry(row).or_insert_with(|| {
let mut size = snapshot.indent_size_for_line(row);
@@ -1223,12 +1223,17 @@ impl Buffer {
} else {
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
};
+ let (start_columns, is_block_mode) = match mode {
+ AutoindentMode::Block { start_columns } => (start_columns, true),
+ AutoindentMode::Independent => (Default::default(), false),
+ };
let mut delta = 0isize;
let entries = edits
.into_iter()
+ .enumerate()
.zip(&edit_operation.as_edit().unwrap().new_text)
- .map(|((range, _), new_text)| {
+ .map(|((ix, (range, _)), new_text)| {
let new_text_len = new_text.len();
let old_start = range.start.to_point(&before_edit);
let new_start = (delta + range.start as isize) as usize;
@@ -1236,7 +1241,7 @@ impl Buffer {
let mut range_of_insertion_to_indent = 0..new_text_len;
let mut first_line_is_new = false;
- let mut original_indent = None;
+ let mut start_column = None;
// When inserting an entire line at the beginning of an existing line,
// treat the insertion as new.
@@ -1254,8 +1259,10 @@ impl Buffer {
}
// Avoid auto-indenting before the insertion.
- if matches!(mode, AutoindentMode::Block) {
- original_indent = Some(indent_size_for_text(new_text.chars()));
+ if is_block_mode {
+ start_column = start_columns
+ .get(ix)
+ .map(|start| start + indent_size_for_text(new_text.chars()).len);
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
range_of_insertion_to_indent.end -= 1;
}
@@ -1263,7 +1270,7 @@ impl Buffer {
AutoindentRequestEntry {
first_line_is_new,
- original_indent,
+ start_column,
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
}
@@ -1274,7 +1281,7 @@ impl Buffer {
before_edit,
entries,
indent_size,
- mode,
+ is_block_mode,
}));
}
@@ -944,7 +944,9 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
// 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),
+ Some(AutoindentMode::Block {
+ start_columns: vec![0],
+ }),
cx,
);
assert_eq!(
@@ -967,7 +969,9 @@ fn test_autoindent_block_mode(cx: &mut MutableAppContext) {
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 {
+ start_columns: vec![0],
+ }),
cx,
);
assert_eq!(
@@ -1,5 +1,6 @@
use editor::{ClipboardSelection, Editor};
use gpui::{ClipboardItem, MutableAppContext};
+use std::cmp;
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut MutableAppContext) {
let selections = editor.selections.all_adjusted(cx);
@@ -17,6 +18,10 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut Mut
clipboard_selections.push(ClipboardSelection {
len: text.len() - initial_len,
is_entire_line: linewise,
+ first_line_indent: cmp::min(
+ start.column,
+ buffer.indent_size_for_line(start.row).len,
+ ),
});
}
}