mirror of
https://github.com/ilyakooo0/helix.git
synced 2025-01-08 16:01:31 +03:00
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 in48a3965ab4
. 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.
This commit is contained in:
parent
fd585c1ee4
commit
c74b97447f
@ -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…
Reference in New Issue
Block a user