Handle paste correctly when there is only one full-line in the clipboard

This commit is contained in:
Antonio Scandurra 2021-04-14 11:15:55 +02:00
parent e082935076
commit 4a395314b2

View file

@ -18,6 +18,7 @@ use smol::Timer;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
fmt::Write, fmt::Write,
iter::FromIterator,
ops::Range, ops::Range,
sync::Arc, sync::Arc,
time::Duration, time::Duration,
@ -499,10 +500,13 @@ impl BufferView {
selection.start = buffer.anchor_before(start).unwrap(); selection.start = buffer.anchor_before(start).unwrap();
selection.end = buffer.anchor_after(end).unwrap(); selection.end = buffer.anchor_after(end).unwrap();
} }
let prev_len = text.len(); let mut len = 0;
text.extend(buffer.text_for_range(start..end).unwrap()); for ch in buffer.text_for_range(start..end).unwrap() {
text.push(ch);
len += 1;
}
clipboard_selections.push(ClipboardSelection { clipboard_selections.push(ClipboardSelection {
len: text.len() - prev_len, len,
is_entire_line, is_entire_line,
}); });
} }
@ -521,7 +525,7 @@ impl BufferView {
let max_point = buffer.max_point(); let max_point = buffer.max_point();
let mut text = String::new(); let mut text = String::new();
let selections = self.selections(ctx.app()); let selections = self.selections(ctx.app());
let mut selection_lengths = Vec::with_capacity(selections.len()); let mut clipboard_selections = Vec::with_capacity(selections.len());
for selection in selections { for selection in selections {
let mut start = selection.start.to_point(buffer).expect("invalid start"); let mut start = selection.start.to_point(buffer).expect("invalid start");
let mut end = selection.end.to_point(buffer).expect("invalid end"); let mut end = selection.end.to_point(buffer).expect("invalid end");
@ -530,86 +534,109 @@ impl BufferView {
start = Point::new(start.row, 0); start = Point::new(start.row, 0);
end = cmp::min(max_point, Point::new(start.row + 1, 0)); end = cmp::min(max_point, Point::new(start.row + 1, 0));
} }
let prev_len = text.len(); let mut len = 0;
text.extend(buffer.text_for_range(start..end).unwrap()); for ch in buffer.text_for_range(start..end).unwrap() {
selection_lengths.push(ClipboardSelection { text.push(ch);
len: text.len() - prev_len, len += 1;
}
clipboard_selections.push(ClipboardSelection {
len,
is_entire_line, is_entire_line,
}); });
} }
ctx.app_mut() ctx.app_mut()
.write_to_clipboard(ClipboardItem::new(text).with_metadata(selection_lengths)); .write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
} }
pub fn paste(&mut self, _: &(), ctx: &mut ViewContext<Self>) { pub fn paste(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
if let Some(item) = ctx.app_mut().read_from_clipboard() { if let Some(item) = ctx.app_mut().read_from_clipboard() {
let clipboard_text = item.text(); let clipboard_text = item.text();
if let Some(clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() { if let Some(clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
// If there are the same number of selections as there were at the let selections_len = self.selections(ctx.app()).len();
// time that this clipboard data was written, then paste one slice of the if clipboard_selections.len() == selections_len {
// clipboard text into each of the current selections. // If there are the same number of selections as there were at the time that
let selections = self.selections(ctx.app()).to_vec(); // this clipboard data was written, then paste one slice of the clipboard text
if clipboard_selections.len() == selections.len() { // into each of the current selections.
self.start_transaction(ctx); self.multiline_paste(clipboard_text.chars(), clipboard_selections.iter(), ctx);
let mut new_selections = Vec::with_capacity(selections.len()); } else if clipboard_selections.len() == 1 && clipboard_selections[0].is_entire_line
let mut clipboard_offset = 0; {
for (i, selection) in selections.iter().enumerate() { // If there was only one selection in the clipboard but it spanned the whole
let clipboard_selection = &clipboard_selections[i]; // line, then paste it over and over into each of the current selections so that
let clipboard_slice = &clipboard_text // we can position it before the selections that are empty.
[clipboard_offset..(clipboard_offset + clipboard_selection.len)]; self.multiline_paste(
clipboard_offset = clipboard_offset + clipboard_selection.len; clipboard_text.chars().cycle(),
clipboard_selections.iter().cycle(),
self.buffer.update(ctx, |buffer, ctx| { ctx,
let selection_start = selection.start.to_point(buffer).unwrap(); );
let selection_end = selection.end.to_point(buffer).unwrap(); } else {
let char_count = clipboard_slice.chars().count(); self.insert(clipboard_text, ctx);
let max_point = buffer.max_point();
// If the corresponding selection was empty when this slice of the
// clipboard text was written, then the entire line containing the
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let anchor;
if selection_start == selection_end
&& clipboard_selection.is_entire_line
{
let start_point = Point::new(selection_start.row, 0);
let start = start_point.to_offset(buffer).unwrap();
let new_position = cmp::min(
max_point,
Point::new(start_point.row + 1, selection_start.column),
);
buffer
.edit(Some(start..start), clipboard_slice, Some(ctx))
.unwrap();
anchor = buffer.anchor_before(new_position).unwrap();
} else {
let start = selection.start.to_offset(buffer).unwrap();
let end = selection.end.to_offset(buffer).unwrap();
buffer
.edit(Some(start..end), clipboard_slice, Some(ctx))
.unwrap();
anchor = buffer.anchor_before(start + char_count).unwrap();
}
new_selections.push(Selection {
start: anchor.clone(),
end: anchor,
reversed: false,
goal_column: None,
});
});
}
self.update_selections(new_selections, ctx);
self.end_transaction(ctx);
return;
} }
} else {
self.insert(clipboard_text, ctx);
} }
self.insert(item.text(), ctx);
} }
} }
fn multiline_paste<'a>(
&mut self,
mut clipboard_text: impl Iterator<Item = char>,
clipboard_selections: impl Iterator<Item = &'a ClipboardSelection>,
ctx: &mut ViewContext<Self>,
) {
self.start_transaction(ctx);
let selections = self.selections(ctx.app()).to_vec();
let mut new_selections = Vec::with_capacity(selections.len());
let mut clipboard_offset = 0;
for (selection, clipboard_selection) in selections.iter().zip(clipboard_selections) {
let clipboard_slice =
String::from_iter(clipboard_text.by_ref().take(clipboard_selection.len));
clipboard_offset = clipboard_offset + clipboard_selection.len;
self.buffer.update(ctx, |buffer, ctx| {
let selection_start = selection.start.to_point(buffer).unwrap();
let selection_end = selection.end.to_point(buffer).unwrap();
let max_point = buffer.max_point();
// If the corresponding selection was empty when this slice of the
// clipboard text was written, then the entire line containing the
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let anchor;
if selection_start == selection_end && clipboard_selection.is_entire_line {
let start_point = Point::new(selection_start.row, 0);
let start = start_point.to_offset(buffer).unwrap();
let new_position = cmp::min(
max_point,
Point::new(start_point.row + 1, selection_start.column),
);
buffer
.edit(Some(start..start), clipboard_slice, Some(ctx))
.unwrap();
anchor = buffer.anchor_before(new_position).unwrap();
} else {
let start = selection.start.to_offset(buffer).unwrap();
let end = selection.end.to_offset(buffer).unwrap();
buffer
.edit(Some(start..end), clipboard_slice, Some(ctx))
.unwrap();
anchor = buffer
.anchor_before(start + clipboard_selection.len)
.unwrap();
}
new_selections.push(Selection {
start: anchor.clone(),
end: anchor,
reversed: false,
goal_column: None,
});
});
}
self.update_selections(new_selections, ctx);
self.end_transaction(ctx);
}
pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) { pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
self.buffer self.buffer
.update(ctx, |buffer, ctx| buffer.undo(Some(ctx))); .update(ctx, |buffer, ctx| buffer.undo(Some(ctx)));