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 | |
|
||||
| `paste_after` | Paste after 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_before` | Paste clipboard before selections | normal: `` <space>P ``, select: `` <space>P `` |
|
||||
| `paste_primary_clipboard_after` | Paste primary clipboard after selections | |
|
||||
|
|
|
@ -488,6 +488,8 @@ impl MappableCommand {
|
|||
replace_with_yanked, "Replace with yanked text",
|
||||
replace_selections_with_clipboard, "Replace selections by clipboard content",
|
||||
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_before, "Paste before selection",
|
||||
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());
|
||||
|
||||
/// 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(
|
||||
values: &[String],
|
||||
doc: &mut Document,
|
||||
|
@ -4633,6 +4644,7 @@ fn paste_impl(
|
|||
action: Paste,
|
||||
count: usize,
|
||||
mode: Mode,
|
||||
paste_type: PasteType,
|
||||
) {
|
||||
if values.is_empty() {
|
||||
return;
|
||||
|
@ -4661,7 +4673,7 @@ fn paste_impl(
|
|||
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 selection = doc.selection(view.id);
|
||||
|
@ -4669,6 +4681,12 @@ fn paste_impl(
|
|||
let mut offset = 0;
|
||||
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 pos = match (action, linewise) {
|
||||
// paste linewise before
|
||||
|
@ -4686,23 +4704,53 @@ fn paste_impl(
|
|||
(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
|
||||
.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;
|
||||
}
|
||||
|
||||
let new_range = Range::new(anchor, anchor + value_len).with_direction(range.direction());
|
||||
ranges.push(new_range);
|
||||
offset += value_len;
|
||||
// Return the precomputed tendril value
|
||||
combined_tendril_value.clone()
|
||||
}
|
||||
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)
|
||||
});
|
||||
|
||||
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);
|
||||
|
@ -4716,27 +4764,47 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
|
|||
Mode::Normal => Paste::Before,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -4803,14 +4871,38 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
|
|||
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 {
|
||||
return;
|
||||
};
|
||||
let values: Vec<_> = values.map(|value| value.to_string()).collect();
|
||||
|
||||
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) {
|
||||
|
@ -4820,6 +4912,7 @@ fn paste_after(cx: &mut Context) {
|
|||
.unwrap_or(cx.editor.config().default_yank_register),
|
||||
Paste::After,
|
||||
cx.count(),
|
||||
PasteType::Default,
|
||||
);
|
||||
exit_select_mode(cx);
|
||||
}
|
||||
|
@ -4831,6 +4924,7 @@ fn paste_before(cx: &mut Context) {
|
|||
.unwrap_or(cx.editor.config().default_yank_register),
|
||||
Paste::Before,
|
||||
cx.count(),
|
||||
PasteType::Default,
|
||||
);
|
||||
exit_select_mode(cx);
|
||||
}
|
||||
|
@ -5710,6 +5804,7 @@ fn insert_register(cx: &mut Context) {
|
|||
.unwrap_or(cx.editor.config().default_yank_register),
|
||||
Paste::Cursor,
|
||||
cx.count(),
|
||||
PasteType::Default,
|
||||
);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1039,7 +1039,7 @@ fn paste_clipboard_after(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
paste(cx.editor, '+', Paste::After, 1);
|
||||
paste(cx.editor, '+', Paste::After, 1, PasteType::Default);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1052,7 +1052,7 @@ fn paste_clipboard_before(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
paste(cx.editor, '+', Paste::Before, 1);
|
||||
paste(cx.editor, '+', Paste::Before, 1, PasteType::Default);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1065,7 +1065,7 @@ fn paste_primary_clipboard_after(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
paste(cx.editor, '*', Paste::After, 1);
|
||||
paste(cx.editor, '*', Paste::After, 1, PasteType::Default);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1078,7 +1078,7 @@ fn paste_primary_clipboard_before(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
paste(cx.editor, '*', Paste::Before, 1);
|
||||
paste(cx.editor, '*', Paste::Before, 1, PasteType::Default);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -152,7 +152,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
|||
"y" => yank,
|
||||
// yank_all
|
||||
"p" => paste_after,
|
||||
// paste_all
|
||||
"P" => paste_before,
|
||||
|
||||
"Q" => record_macro,
|
||||
|
|
|
@ -4,6 +4,7 @@ use super::*;
|
|||
|
||||
mod insert;
|
||||
mod movement;
|
||||
mod paste_all_selections;
|
||||
mod write;
|
||||
|
||||
#[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