fix: replace selected ranges instead of ignoring visual paste
This commit is contained in:
+94
-6
@@ -336,6 +336,7 @@ enum EditStart {
|
||||
after: bool,
|
||||
fragments: Vec<String>,
|
||||
linewise: bool,
|
||||
selector: Option<SelectionIntent>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -591,17 +592,25 @@ core_actions![
|
||||
Action::ChangeWordBackward,
|
||||
&[Mode::Normal]
|
||||
),
|
||||
("core.paste_before", Action::PasteBefore, &[Mode::Normal]),
|
||||
("core.paste_after", Action::PasteAfter, &[Mode::Normal]),
|
||||
(
|
||||
"core.paste_before",
|
||||
Action::PasteBefore,
|
||||
&[Mode::Normal, Mode::Visual]
|
||||
),
|
||||
(
|
||||
"core.paste_after",
|
||||
Action::PasteAfter,
|
||||
&[Mode::Normal, Mode::Visual]
|
||||
),
|
||||
(
|
||||
"core.paste_system_before",
|
||||
Action::PasteSystemBefore,
|
||||
&[Mode::Normal],
|
||||
&[Mode::Normal, Mode::Visual],
|
||||
),
|
||||
(
|
||||
"core.paste_system_after",
|
||||
Action::PasteSystemAfter,
|
||||
&[Mode::Normal],
|
||||
&[Mode::Normal, Mode::Visual],
|
||||
),
|
||||
(
|
||||
"core.left",
|
||||
@@ -1270,6 +1279,8 @@ fn default_bindings() -> HashMap<Mode, HashMap<Input, Binding>> {
|
||||
(Input::Char('d'), Cut),
|
||||
(Input::Char('c'), Change),
|
||||
(Input::Char('x'), Cut),
|
||||
(Input::Char('p'), PasteAfter),
|
||||
(Input::Char('P'), PasteBefore),
|
||||
(Input::Delete, Cut),
|
||||
(Input::Char('I'), InsertFirstNonblank),
|
||||
(Input::Char('A'), AppendLineEnd),
|
||||
@@ -8802,6 +8813,8 @@ impl Editor {
|
||||
self.set_message("Clipboard fragment count does not match caret count");
|
||||
} else {
|
||||
let before = self.snapshot();
|
||||
let ranges = self.selections();
|
||||
let selector = (!ranges.is_empty()).then(|| self.selection_intent(&ranges));
|
||||
if let Err(error) = self.paste_fragments(after, &fragments, linewise) {
|
||||
self.set_message(error);
|
||||
if !system {
|
||||
@@ -8814,6 +8827,7 @@ impl Editor {
|
||||
start: EditStart::Paste {
|
||||
after,
|
||||
linewise,
|
||||
selector,
|
||||
fragments: if system {
|
||||
std::mem::take(&mut fragments)
|
||||
} else {
|
||||
@@ -8837,6 +8851,32 @@ impl Editor {
|
||||
linewise: bool,
|
||||
) -> Result<(), &'static str> {
|
||||
let offsets = line_offsets(&self.active.lines);
|
||||
if self.active.view.mode == Mode::Visual {
|
||||
let edits = self
|
||||
.active
|
||||
.view
|
||||
.targets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(target, item)| {
|
||||
let range = self.target_range(*item);
|
||||
let text = &fragments[if fragments.len() == 1 { 0 } else { target }];
|
||||
Edit {
|
||||
target: Some(target),
|
||||
start: cursor_offset(&self.active.lines, &offsets, range.start),
|
||||
end: cursor_offset(&self.active.lines, &offsets, range.end),
|
||||
text: Cow::Borrowed(text),
|
||||
caret: text.len(),
|
||||
round_up: true,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.active.view.mode = Mode::Insert;
|
||||
self.active.view.linewise = false;
|
||||
self.apply_edits(edits);
|
||||
self.leave_insert();
|
||||
return Ok(());
|
||||
}
|
||||
if linewise {
|
||||
if fragments.len() != 1
|
||||
&& self
|
||||
@@ -9241,7 +9281,24 @@ impl Editor {
|
||||
after,
|
||||
fragments,
|
||||
linewise,
|
||||
} => self.paste_fragments(*after, fragments, *linewise)?,
|
||||
selector,
|
||||
} => {
|
||||
if let Some(selector) = selector {
|
||||
let count = match selector {
|
||||
SelectionIntent::Spans(spans) => spans.len(),
|
||||
SelectionIntent::Rows(rows) => rows.len(),
|
||||
SelectionIntent::Motions { .. } => 1,
|
||||
};
|
||||
if self.active.view.mode != Mode::Visual
|
||||
&& count != 1
|
||||
&& count != self.active.view.targets.len()
|
||||
{
|
||||
return Err("Repeat span count does not match target count");
|
||||
}
|
||||
self.replay_ranges(selector);
|
||||
}
|
||||
self.paste_fragments(*after, fragments, *linewise)?;
|
||||
}
|
||||
}
|
||||
for step in &recipe.steps {
|
||||
match step {
|
||||
@@ -16367,6 +16424,38 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_system_paste_replaces_only_after_successful_read_without_publishing() {
|
||||
for (key, action) in [
|
||||
('p', None),
|
||||
('P', None),
|
||||
('z', Some("core.paste_system_before")),
|
||||
('z', Some("core.paste_system_after")),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
let mut editor = fixture.editor();
|
||||
if let Some(action) = action {
|
||||
editor.bind("visual", "z", Some(action.into())).unwrap();
|
||||
}
|
||||
editor.apply(vec![Effect::Replace("abc".into())]).unwrap();
|
||||
fixture.external("X");
|
||||
type_text(&mut editor, "vl");
|
||||
fixture.fail("fail-read", true);
|
||||
fixture.fail("fail-write", true);
|
||||
let before = state(&editor);
|
||||
editor.handle(Input::Char(key));
|
||||
assert_eq!(state(&editor), before);
|
||||
fixture.fail("fail-read", false);
|
||||
editor.handle(Input::Char(key));
|
||||
assert_eq!(editor.context().text, "Xc");
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
assert_eq!(fixture.bytes("state"), b"X");
|
||||
assert!(fixture.bytes("writes").is_empty());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "abc");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linewise_owned_system_bytes_keep_shape_external_changes_do_not_guess_it() {
|
||||
let fixture = Fixture::new();
|
||||
@@ -16632,7 +16721,6 @@ mod tests {
|
||||
fixture.fail("fail-read", false);
|
||||
fixture.fail("fail-write", false);
|
||||
for action in ["core.paste_system_before", "core.paste_system_after"] {
|
||||
assert!(editor.bind("visual", "z", Some(action.into())).is_err());
|
||||
assert!(editor.bind("insert", "z", Some(action.into())).is_err());
|
||||
editor.bind("normal", "z", Some(action.into())).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user