mirror of https://github.com/helix-editor/helix
Merge f8457af260
into 205e7ece70
commit
99ca0fe5d9
|
@ -192,6 +192,8 @@
|
||||||
| `replace_selections_with_primary_clipboard` | Replace selections by primary clipboard | |
|
| `replace_selections_with_primary_clipboard` | Replace selections by primary clipboard | |
|
||||||
| `paste_after` | Paste after selection | normal: `` p ``, select: `` p `` |
|
| `paste_after` | Paste after selection | normal: `` p ``, select: `` p `` |
|
||||||
| `paste_before` | Paste before selection | normal: `` P ``, select: `` P `` |
|
| `paste_before` | Paste before selection | normal: `` P ``, select: `` P `` |
|
||||||
|
| `paste_all_selections_after` | Paste all selections after each selection, presering selections | |
|
||||||
|
| `paste_all_selections_before` | Paste all selections before each selection, presering selections | |
|
||||||
| `paste_clipboard_after` | Paste clipboard after selections | normal: `` <space>p ``, select: `` <space>p `` |
|
| `paste_clipboard_after` | Paste clipboard after selections | normal: `` <space>p ``, select: `` <space>p `` |
|
||||||
| `paste_clipboard_before` | Paste clipboard before selections | normal: `` <space>P ``, select: `` <space>P `` |
|
| `paste_clipboard_before` | Paste clipboard before selections | normal: `` <space>P ``, select: `` <space>P `` |
|
||||||
| `paste_primary_clipboard_after` | Paste primary clipboard after selections | |
|
| `paste_primary_clipboard_after` | Paste primary clipboard after selections | |
|
||||||
|
|
|
@ -488,6 +488,8 @@ impl MappableCommand {
|
||||||
replace_with_yanked, "Replace with yanked text",
|
replace_with_yanked, "Replace with yanked text",
|
||||||
replace_selections_with_clipboard, "Replace selections by clipboard content",
|
replace_selections_with_clipboard, "Replace selections by clipboard content",
|
||||||
replace_selections_with_primary_clipboard, "Replace selections by primary clipboard",
|
replace_selections_with_primary_clipboard, "Replace selections by primary clipboard",
|
||||||
|
paste_all_selections_after, "Paste all after selection",
|
||||||
|
paste_all_selections_before, "Paste all before selection",
|
||||||
paste_after, "Paste after selection",
|
paste_after, "Paste after selection",
|
||||||
paste_before, "Paste before selection",
|
paste_before, "Paste before selection",
|
||||||
paste_clipboard_after, "Paste clipboard after selections",
|
paste_clipboard_after, "Paste clipboard after selections",
|
||||||
|
@ -4626,6 +4628,15 @@ enum Paste {
|
||||||
|
|
||||||
static LINE_ENDING_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
|
static LINE_ENDING_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
|
||||||
|
|
||||||
|
/// Enumerates the possible ways to paste
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum PasteType {
|
||||||
|
/// Paste one value in the register per selection
|
||||||
|
Default,
|
||||||
|
/// Paste every value in the register for every selection
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
fn paste_impl(
|
fn paste_impl(
|
||||||
values: &[String],
|
values: &[String],
|
||||||
doc: &mut Document,
|
doc: &mut Document,
|
||||||
|
@ -4633,6 +4644,7 @@ fn paste_impl(
|
||||||
action: Paste,
|
action: Paste,
|
||||||
count: usize,
|
count: usize,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
paste_type: PasteType,
|
||||||
) {
|
) {
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -4661,7 +4673,7 @@ fn paste_impl(
|
||||||
map_value(values.last().unwrap()),
|
map_value(values.last().unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut values = values.iter().map(|value| map_value(value)).chain(repeat);
|
let mut values_iter = 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);
|
||||||
|
@ -4669,6 +4681,12 @@ fn paste_impl(
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut ranges = SmallVec::with_capacity(selection.len());
|
let mut ranges = SmallVec::with_capacity(selection.len());
|
||||||
|
|
||||||
|
// Precompute the combined tendril value if we will be doing a PasteType::All
|
||||||
|
let combined_tendril_value = match paste_type {
|
||||||
|
PasteType::All => Some(values.iter().collect::<Tendril>()),
|
||||||
|
PasteType::Default => None,
|
||||||
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::change_by_selection(text, selection, |range| {
|
let mut transaction = Transaction::change_by_selection(text, selection, |range| {
|
||||||
let pos = match (action, linewise) {
|
let pos = match (action, linewise) {
|
||||||
// paste linewise before
|
// paste linewise before
|
||||||
|
@ -4686,23 +4704,53 @@ fn paste_impl(
|
||||||
(Paste::Cursor, _) => range.cursor(text.slice(..)),
|
(Paste::Cursor, _) => range.cursor(text.slice(..)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = values.next();
|
let value = match paste_type {
|
||||||
|
PasteType::All => {
|
||||||
|
// Iterate over every value and add its range
|
||||||
|
for value in values {
|
||||||
|
let value_len = value.chars().count();
|
||||||
|
let anchor = offset + pos;
|
||||||
|
|
||||||
let value_len = value
|
let new_range =
|
||||||
.as_ref()
|
Range::new(anchor, anchor + value_len).with_direction(range.direction());
|
||||||
.map(|content| content.chars().count())
|
ranges.push(new_range);
|
||||||
.unwrap_or_default();
|
offset += value_len;
|
||||||
let anchor = offset + pos;
|
}
|
||||||
|
|
||||||
let new_range = Range::new(anchor, anchor + value_len).with_direction(range.direction());
|
// Return the precomputed tendril value
|
||||||
ranges.push(new_range);
|
combined_tendril_value.clone()
|
||||||
offset += value_len;
|
}
|
||||||
|
PasteType::Default => {
|
||||||
|
let value: Option<Tendril> = values_iter.next();
|
||||||
|
|
||||||
|
let value_len = value
|
||||||
|
.as_ref()
|
||||||
|
.map(|content| content.chars().count())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let anchor = offset + pos;
|
||||||
|
|
||||||
|
let new_range =
|
||||||
|
Range::new(anchor, anchor + value_len).with_direction(range.direction());
|
||||||
|
ranges.push(new_range);
|
||||||
|
offset += value_len;
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(pos, pos, value)
|
(pos, pos, value)
|
||||||
});
|
});
|
||||||
|
|
||||||
if mode == Mode::Normal {
|
if mode == Mode::Normal {
|
||||||
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
|
transaction = transaction.with_selection(Selection::new(
|
||||||
|
ranges,
|
||||||
|
match paste_type {
|
||||||
|
PasteType::All => {
|
||||||
|
// Last selection pasted for the previous primary selection
|
||||||
|
(selection.primary_index() * values.len()) + (values.len() - 1)
|
||||||
|
}
|
||||||
|
PasteType::Default => selection.primary_index(),
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
|
@ -4716,27 +4764,47 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
|
||||||
Mode::Normal => Paste::Before,
|
Mode::Normal => Paste::Before,
|
||||||
};
|
};
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
|
paste_impl(
|
||||||
|
&[contents],
|
||||||
|
doc,
|
||||||
|
view,
|
||||||
|
paste,
|
||||||
|
count,
|
||||||
|
cx.editor.mode,
|
||||||
|
PasteType::Default,
|
||||||
|
);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_clipboard_after(cx: &mut Context) {
|
fn paste_clipboard_after(cx: &mut Context) {
|
||||||
paste(cx.editor, '+', Paste::After, cx.count());
|
paste(cx.editor, '+', Paste::After, cx.count(), PasteType::Default);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_clipboard_before(cx: &mut Context) {
|
fn paste_clipboard_before(cx: &mut Context) {
|
||||||
paste(cx.editor, '+', Paste::Before, cx.count());
|
paste(
|
||||||
|
cx.editor,
|
||||||
|
'+',
|
||||||
|
Paste::Before,
|
||||||
|
cx.count(),
|
||||||
|
PasteType::Default,
|
||||||
|
);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_primary_clipboard_after(cx: &mut Context) {
|
fn paste_primary_clipboard_after(cx: &mut Context) {
|
||||||
paste(cx.editor, '*', Paste::After, cx.count());
|
paste(cx.editor, '*', Paste::After, cx.count(), PasteType::Default);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_primary_clipboard_before(cx: &mut Context) {
|
fn paste_primary_clipboard_before(cx: &mut Context) {
|
||||||
paste(cx.editor, '*', Paste::Before, cx.count());
|
paste(
|
||||||
|
cx.editor,
|
||||||
|
'*',
|
||||||
|
Paste::Before,
|
||||||
|
cx.count(),
|
||||||
|
PasteType::Default,
|
||||||
|
);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4803,14 +4871,38 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
|
fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize, paste_type: PasteType) {
|
||||||
let Some(values) = editor.registers.read(register, editor) else {
|
let Some(values) = editor.registers.read(register, editor) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let values: Vec<_> = values.map(|value| value.to_string()).collect();
|
let values: Vec<_> = values.map(|value| value.to_string()).collect();
|
||||||
|
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
paste_impl(&values, doc, view, pos, count, editor.mode);
|
paste_impl(&values, doc, view, pos, count, editor.mode, paste_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste_all_selections_after(cx: &mut Context) {
|
||||||
|
paste(
|
||||||
|
cx.editor,
|
||||||
|
cx.register
|
||||||
|
.unwrap_or(cx.editor.config().default_yank_register),
|
||||||
|
Paste::After,
|
||||||
|
cx.count(),
|
||||||
|
PasteType::All,
|
||||||
|
);
|
||||||
|
exit_select_mode(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste_all_selections_before(cx: &mut Context) {
|
||||||
|
paste(
|
||||||
|
cx.editor,
|
||||||
|
cx.register
|
||||||
|
.unwrap_or(cx.editor.config().default_yank_register),
|
||||||
|
Paste::Before,
|
||||||
|
cx.count(),
|
||||||
|
PasteType::All,
|
||||||
|
);
|
||||||
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_after(cx: &mut Context) {
|
fn paste_after(cx: &mut Context) {
|
||||||
|
@ -4820,6 +4912,7 @@ fn paste_after(cx: &mut Context) {
|
||||||
.unwrap_or(cx.editor.config().default_yank_register),
|
.unwrap_or(cx.editor.config().default_yank_register),
|
||||||
Paste::After,
|
Paste::After,
|
||||||
cx.count(),
|
cx.count(),
|
||||||
|
PasteType::Default,
|
||||||
);
|
);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
@ -4831,6 +4924,7 @@ fn paste_before(cx: &mut Context) {
|
||||||
.unwrap_or(cx.editor.config().default_yank_register),
|
.unwrap_or(cx.editor.config().default_yank_register),
|
||||||
Paste::Before,
|
Paste::Before,
|
||||||
cx.count(),
|
cx.count(),
|
||||||
|
PasteType::Default,
|
||||||
);
|
);
|
||||||
exit_select_mode(cx);
|
exit_select_mode(cx);
|
||||||
}
|
}
|
||||||
|
@ -5710,6 +5804,7 @@ fn insert_register(cx: &mut Context) {
|
||||||
.unwrap_or(cx.editor.config().default_yank_register),
|
.unwrap_or(cx.editor.config().default_yank_register),
|
||||||
Paste::Cursor,
|
Paste::Cursor,
|
||||||
cx.count(),
|
cx.count(),
|
||||||
|
PasteType::Default,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1039,7 +1039,7 @@ fn paste_clipboard_after(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
paste(cx.editor, '+', Paste::After, 1);
|
paste(cx.editor, '+', Paste::After, 1, PasteType::Default);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1052,7 +1052,7 @@ fn paste_clipboard_before(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
paste(cx.editor, '+', Paste::Before, 1);
|
paste(cx.editor, '+', Paste::Before, 1, PasteType::Default);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1065,7 +1065,7 @@ fn paste_primary_clipboard_after(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
paste(cx.editor, '*', Paste::After, 1);
|
paste(cx.editor, '*', Paste::After, 1, PasteType::Default);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,7 +1078,7 @@ fn paste_primary_clipboard_before(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
paste(cx.editor, '*', Paste::Before, 1);
|
paste(cx.editor, '*', Paste::Before, 1, PasteType::Default);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
"y" => yank,
|
"y" => yank,
|
||||||
// yank_all
|
// yank_all
|
||||||
"p" => paste_after,
|
"p" => paste_after,
|
||||||
// paste_all
|
|
||||||
"P" => paste_before,
|
"P" => paste_before,
|
||||||
|
|
||||||
"Q" => record_macro,
|
"Q" => record_macro,
|
||||||
|
|
|
@ -4,6 +4,7 @@ use super::*;
|
||||||
|
|
||||||
mod insert;
|
mod insert;
|
||||||
mod movement;
|
mod movement;
|
||||||
|
mod paste_all_selections;
|
||||||
mod write;
|
mod write;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use helix_core::hashmap;
|
||||||
|
use helix_term::keymap;
|
||||||
|
use helix_view::document::Mode;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn paste_all_selections() -> anyhow::Result<()> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.keys.insert(
|
||||||
|
Mode::Normal,
|
||||||
|
keymap!({"Normal Mode"
|
||||||
|
"A-P" => paste_all_selections_before,
|
||||||
|
"A-p" => paste_all_selections_after,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test paste_all_selections_before
|
||||||
|
// Selection direction matches paste target selections.
|
||||||
|
test_with_config(
|
||||||
|
AppBuilder::new().with_config(config.clone()),
|
||||||
|
(
|
||||||
|
indoc! {"\
|
||||||
|
#(|one)#_cat
|
||||||
|
#(|two)#_dog
|
||||||
|
#[|three]#_cow
|
||||||
|
"},
|
||||||
|
"y<A-P>",
|
||||||
|
indoc! {"\
|
||||||
|
#(|one)##(|two)##(|three)#one_cat
|
||||||
|
#(|one)##(|two)##(|three)#two_dog
|
||||||
|
#(|one)##(|two)##[|three]#three_cow
|
||||||
|
"},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Test paste_all_selections_after
|
||||||
|
// Primary selection is last selection pasted for previous primary.
|
||||||
|
test_with_config(
|
||||||
|
AppBuilder::new().with_config(config.clone()),
|
||||||
|
(
|
||||||
|
indoc! {"\
|
||||||
|
#(one|)#_cat
|
||||||
|
#[two|]#_dog
|
||||||
|
#(three|)#_cow
|
||||||
|
"},
|
||||||
|
"y<A-p>",
|
||||||
|
indoc! {"\
|
||||||
|
one#(one|)##(two|)##(three|)#_cat
|
||||||
|
two#(one|)##(two|)##[three|]#_dog
|
||||||
|
three#(one|)##(two|)##(three|)#_cow
|
||||||
|
"},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue