aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/commands.rs24
-rw-r--r--helix-term/tests/test/commands.rs68
2 files changed, 86 insertions, 6 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c74dfb39..5498a437 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -4773,6 +4773,8 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
let mut ranges = SmallVec::with_capacity(selection.len());
let text = doc.text().slice(..);
+ let mut offset = 0isize;
+
for range in selection.ranges() {
let fragment = range.slice(text);
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.into())) {
@@ -4788,13 +4790,23 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
return;
}
- let (from, to) = match behavior {
- ShellBehavior::Replace => (range.from(), range.to()),
- ShellBehavior::Insert => (range.from(), range.from()),
- ShellBehavior::Append => (range.to(), range.to()),
- _ => (range.from(), range.from()),
+ let output_len = output.chars().count();
+
+ let (from, to, deleted_len) = match behavior {
+ ShellBehavior::Replace => (range.from(), range.to(), range.len()),
+ ShellBehavior::Insert => (range.from(), range.from(), 0),
+ ShellBehavior::Append => (range.to(), range.to(), 0),
+ _ => (range.from(), range.from(), 0),
};
- ranges.push(Range::new(to, to + output.chars().count()));
+
+ // These `usize`s cannot underflow because selection ranges cannot overlap.
+ // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
+ // arithmetic to assert this.
+ let anchor = (to as isize + offset - deleted_len as isize) as usize;
+ let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
+ ranges.push(new_range);
+ offset = offset + output_len as isize - deleted_len as isize;
+
changes.push((from, to, Some(output)));
}
diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs
index e78e6c9f..114bf222 100644
--- a/helix-term/tests/test/commands.rs
+++ b/helix-term/tests/test/commands.rs
@@ -215,3 +215,71 @@ async fn test_multi_selection_paste() -> anyhow::Result<()> {
Ok(())
}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn test_multi_selection_shell_commands() -> anyhow::Result<()> {
+ // pipe
+ test((
+ platform_line(indoc! {"\
+ #[|lorem]#
+ #(|ipsum)#
+ #(|dolor)#
+ "})
+ .as_str(),
+ "|echo foo<ret>",
+ platform_line(indoc! {"\
+ #[|foo
+ ]#
+ #(|foo
+ )#
+ #(|foo
+ )#
+ "})
+ .as_str(),
+ ))
+ .await?;
+
+ // insert-output
+ test((
+ platform_line(indoc! {"\
+ #[|lorem]#
+ #(|ipsum)#
+ #(|dolor)#
+ "})
+ .as_str(),
+ "!echo foo<ret>",
+ platform_line(indoc! {"\
+ #[|foo
+ ]#lorem
+ #(|foo
+ )#ipsum
+ #(|foo
+ )#dolor
+ "})
+ .as_str(),
+ ))
+ .await?;
+
+ // append-output
+ test((
+ platform_line(indoc! {"\
+ #[|lorem]#
+ #(|ipsum)#
+ #(|dolor)#
+ "})
+ .as_str(),
+ "<A-!>echo foo<ret>",
+ platform_line(indoc! {"\
+ lorem#[|foo
+ ]#
+ ipsum#(|foo
+ )#
+ dolor#(|foo
+ )#
+ "})
+ .as_str(),
+ ))
+ .await?;
+
+ Ok(())
+}