Consistently replace line-endings in paste/replace commands

Previously we replaced line-endings in pasted text to the document
line-ending for some values in paste commands. We missed the `repeat`
values in paste though and didn't do any replacement in the replace
command.

Along with this change I've refactored the replace command to avoid
intermediary collections. We previously eagerly collected the values
from the input register as a `Vec<String>` but we can avoid both of
those conversions and only allocate for the conversion to a `Tendril`.
We can also switch from `str::repeat` to a manual implementation to
avoid the intermediary conversion to a String - this avoids an extra
allocation in the common case (i.e. no count).

Fixes #12329
pull/12151/head
Michael Davis 2024-12-25 11:21:19 -05:00
parent a074129f9c
commit c262fe41ab
No known key found for this signature in database
1 changed files with 35 additions and 26 deletions

View File

@ -4401,6 +4401,8 @@ enum Paste {
Cursor, Cursor,
} }
static LINE_ENDING_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
fn paste_impl( fn paste_impl(
values: &[String], values: &[String],
doc: &mut Document, doc: &mut Document,
@ -4417,26 +4419,26 @@ fn paste_impl(
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
} }
let repeat = std::iter::repeat(
// `values` is asserted to have at least one entry above.
values
.last()
.map(|value| Tendril::from(value.repeat(count)))
.unwrap(),
);
// if any of values ends with a line ending, it's linewise paste // if any of values ends with a line ending, it's linewise paste
let linewise = values let linewise = values
.iter() .iter()
.any(|value| get_line_ending_of_str(value).is_some()); .any(|value| get_line_ending_of_str(value).is_some());
// Only compiled once. let map_value = |value| {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap()); let value = LINE_ENDING_REGEX.replace_all(value, doc.line_ending.as_str());
let mut values = values let mut out = Tendril::from(value.as_ref());
.iter() for _ in 1..count {
.map(|value| REGEX.replace_all(value, doc.line_ending.as_str())) out.push_str(&value);
.map(|value| Tendril::from(value.as_ref().repeat(count))) }
.chain(repeat); out
};
let repeat = std::iter::repeat(
// `values` is asserted to have at least one entry above.
map_value(values.last().unwrap()),
);
let mut values = values.iter().map(|value| map_value(value)).chain(repeat);
let text = doc.text(); let text = doc.text();
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
@ -4533,19 +4535,24 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
else { else {
return; return;
}; };
let values: Vec<_> = values.map(|value| value.to_string()).collect();
let scrolloff = editor.config().scrolloff; let scrolloff = editor.config().scrolloff;
let (view, doc) = current_ref!(editor);
let (view, doc) = current!(editor); let map_value = |value: &Cow<str>| {
let repeat = std::iter::repeat( let value = LINE_ENDING_REGEX.replace_all(value, doc.line_ending.as_str());
values let mut out = Tendril::from(value.as_ref());
.last() for _ in 1..count {
.map(|value| Tendril::from(&value.repeat(count))) out.push_str(&value);
.unwrap(), }
); out
let mut values = values };
.iter() let mut values_rev = values.rev().peekable();
.map(|value| Tendril::from(&value.repeat(count))) // `values` is asserted to have at least one entry above.
let last = values_rev.peek().unwrap();
let repeat = std::iter::repeat(map_value(last));
let mut values = values_rev
.rev()
.map(|value| map_value(&value))
.chain(repeat); .chain(repeat);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
@ -4555,7 +4562,9 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
(range.from(), range.to(), None) (range.from(), range.to(), None)
} }
}); });
drop(values);
let (view, doc) = current!(editor);
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff); view.ensure_cursor_in_view(doc, scrolloff);