Andrew Browne 2025-06-15 13:08:52 +02:00 committed by GitHub
commit 99ca0fe5d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 179 additions and 23 deletions

View File

@ -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 | |

View File

@ -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,7 +4704,24 @@ 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 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()
@ -4694,15 +4729,28 @@ fn paste_impl(
.unwrap_or_default();
let anchor = offset + pos;
let new_range = Range::new(anchor, anchor + value_len).with_direction(range.direction());
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,
);
}
})

View File

@ -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(())
}

View File

@ -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,

View File

@ -4,6 +4,7 @@ use super::*;
mod insert;
mod movement;
mod paste_all_selections;
mod write;
#[tokio::test(flavor = "multi_thread")]

View File

@ -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(())
}