Fix range offsets for multiple shell insertions (#4619)

d6323b7cbc introduced a regression for
shell commands like `|`, `!`, and `<A-!>` which caused the new
selections to be incorrect. This caused a panic when piping (`|`)
would cause the new range to extend past the document end.

The paste version of this bug was fixed in
48a3965ab4.

This change also inherits the direction of the new range from the old
range and adds integration tests to ensure that the behavior isn't
broken in the future.
pull/4584/merge
Michael Davis 2 years ago committed by GitHub
parent fd585c1ee4
commit c74b97447f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

Loading…
Cancel
Save