Skip to content

Commit 7a5b618

Browse files
authored
Fix shell pipe command lines not using expansions (#14191)
1 parent 77ff51c commit 7a5b618

File tree

2 files changed

+66
-67
lines changed

2 files changed

+66
-67
lines changed

helix-term/src/commands.rs

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ pub use typed::*;
2222
use helix_core::{
2323
char_idx_at_visual_offset,
2424
chars::char_is_word,
25-
command_line, comment,
25+
command_line::{self, Args},
26+
comment,
2627
doc_formatter::TextFormat,
2728
encoding, find_workspace,
2829
graphemes::{self, next_grapheme_boundary},
@@ -46,6 +47,7 @@ use helix_core::{
4647
use helix_view::{
4748
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
4849
editor::Action,
50+
expansion,
4951
info::Info,
5052
input::KeyEvent,
5153
keyboard::KeyCode,
@@ -6256,64 +6258,52 @@ enum ShellBehavior {
62566258
}
62576259

62586260
fn shell_pipe(cx: &mut Context) {
6259-
shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace);
6261+
shell_prompt_for_behavior(cx, "pipe:".into(), ShellBehavior::Replace);
62606262
}
62616263

62626264
fn shell_pipe_to(cx: &mut Context) {
6263-
shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore);
6265+
shell_prompt_for_behavior(cx, "pipe-to:".into(), ShellBehavior::Ignore);
62646266
}
62656267

62666268
fn shell_insert_output(cx: &mut Context) {
6267-
shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert);
6269+
shell_prompt_for_behavior(cx, "insert-output:".into(), ShellBehavior::Insert);
62686270
}
62696271

62706272
fn shell_append_output(cx: &mut Context) {
6271-
shell_prompt(cx, "append-output:".into(), ShellBehavior::Append);
6273+
shell_prompt_for_behavior(cx, "append-output:".into(), ShellBehavior::Append);
62726274
}
62736275

62746276
fn shell_keep_pipe(cx: &mut Context) {
6275-
ui::prompt(
6276-
cx,
6277-
"keep-pipe:".into(),
6278-
Some('|'),
6279-
ui::completers::none,
6280-
move |cx, input: &str, event: PromptEvent| {
6281-
let shell = &cx.editor.config().shell;
6282-
if event != PromptEvent::Validate {
6283-
return;
6284-
}
6285-
if input.is_empty() {
6286-
return;
6287-
}
6288-
let (view, doc) = current!(cx.editor);
6289-
let selection = doc.selection(view.id);
6277+
shell_prompt(cx, "keep-pipe:".into(), |cx, args| {
6278+
let shell = &cx.editor.config().shell;
6279+
let (view, doc) = current!(cx.editor);
6280+
let selection = doc.selection(view.id);
62906281

6291-
let mut ranges = SmallVec::with_capacity(selection.len());
6292-
let old_index = selection.primary_index();
6293-
let mut index: Option<usize> = None;
6294-
let text = doc.text().slice(..);
6282+
let mut ranges = SmallVec::with_capacity(selection.len());
6283+
let old_index = selection.primary_index();
6284+
let mut index: Option<usize> = None;
6285+
let text = doc.text().slice(..);
62956286

6296-
for (i, range) in selection.ranges().iter().enumerate() {
6297-
let fragment = range.slice(text);
6298-
if let Err(err) = shell_impl(shell, input, Some(fragment.into())) {
6299-
log::debug!("Shell command failed: {}", err);
6300-
} else {
6301-
ranges.push(*range);
6302-
if i >= old_index && index.is_none() {
6303-
index = Some(ranges.len() - 1);
6304-
}
6287+
for (i, range) in selection.ranges().iter().enumerate() {
6288+
let fragment = range.slice(text);
6289+
if let Err(err) = shell_impl(shell, args.join(" ").as_str(), Some(fragment.into())) {
6290+
log::debug!("Shell command failed: {}", err);
6291+
} else {
6292+
ranges.push(*range);
6293+
if i >= old_index && index.is_none() {
6294+
index = Some(ranges.len() - 1);
63056295
}
63066296
}
6297+
}
63076298

6308-
if ranges.is_empty() {
6309-
cx.editor.set_error("No selections remaining");
6310-
return;
6311-
}
6299+
if ranges.is_empty() {
6300+
cx.editor.set_error("No selections remaining");
6301+
return;
6302+
}
63126303

6313-
let index = index.unwrap_or_else(|| ranges.len() - 1);
6314-
doc.set_selection(view.id, Selection::new(ranges, index));
6315-
},
6316-
);
6304+
let index = index.unwrap_or_else(|| ranges.len() - 1);
6305+
doc.set_selection(view.id, Selection::new(ranges, index));
6306+
});
63176307
}
63186308

63196309
fn shell_impl(shell: &[String], cmd: &str, input: Option<Rope>) -> anyhow::Result<Tendril> {
@@ -6468,25 +6458,35 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
64686458
view.ensure_cursor_in_view(doc, config.scrolloff);
64696459
}
64706460

6471-
fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
6461+
fn shell_prompt<F>(cx: &mut Context, prompt: Cow<'static, str>, mut callback_fn: F)
6462+
where
6463+
F: FnMut(&mut compositor::Context, Args) + 'static,
6464+
{
64726465
ui::prompt(
64736466
cx,
64746467
prompt,
64756468
Some('|'),
6476-
ui::completers::shell,
6477-
move |cx, input: &str, event: PromptEvent| {
6478-
if event != PromptEvent::Validate {
6469+
|editor, input| complete_command_args(editor, SHELL_SIGNATURE, &SHELL_COMPLETER, input, 0),
6470+
move |cx, input, event| {
6471+
if event != PromptEvent::Validate || input.is_empty() {
64796472
return;
64806473
}
6481-
if input.is_empty() {
6482-
return;
6474+
match Args::parse(input, SHELL_SIGNATURE, true, |token| {
6475+
expansion::expand(cx.editor, token).map_err(|err| err.into())
6476+
}) {
6477+
Ok(args) => callback_fn(cx, args),
6478+
Err(err) => cx.editor.set_error(err.to_string()),
64836479
}
6484-
6485-
shell(cx, input, &behavior);
64866480
},
64876481
);
64886482
}
64896483

6484+
fn shell_prompt_for_behavior(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
6485+
shell_prompt(cx, prompt, move |cx, args| {
6486+
shell(cx, args.join(" ").as_str(), &behavior)
6487+
})
6488+
}
6489+
64906490
fn suspend(_cx: &mut Context) {
64916491
#[cfg(not(windows))]
64926492
{

helix-term/src/commands/typed.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@ pub struct TypableCommand {
2929
pub signature: Signature,
3030
}
3131

32-
impl TypableCommand {
33-
fn completer_for_argument_number(&self, n: usize) -> &Completer {
34-
match self.completer.positional_args.get(n) {
35-
Some(completer) => completer,
36-
_ => &self.completer.var_args,
37-
}
38-
}
39-
}
40-
4132
#[derive(Clone)]
4233
pub struct CommandCompleter {
4334
// Arguments with specific completion methods based on their position.
@@ -68,6 +59,13 @@ impl CommandCompleter {
6859
var_args: completer,
6960
}
7061
}
62+
63+
fn for_argument_number(&self, n: usize) -> &Completer {
64+
match self.positional_args.get(n) {
65+
Some(completer) => completer,
66+
_ => &self.var_args,
67+
}
68+
}
7169
}
7270

7371
fn quit(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
@@ -2658,13 +2656,13 @@ const BUFFER_CLOSE_OTHERS_SIGNATURE: Signature = Signature {
26582656
// but Signature does not yet allow for var args.
26592657

26602658
/// This command handles all of its input as-is with no quoting or flags.
2661-
const SHELL_SIGNATURE: Signature = Signature {
2659+
pub const SHELL_SIGNATURE: Signature = Signature {
26622660
positionals: (1, Some(2)),
26632661
raw_after: Some(1),
26642662
..Signature::DEFAULT
26652663
};
26662664

2667-
const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[
2665+
pub const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[
26682666
// Command name
26692667
completers::program,
26702668
// Shell argument(s)
@@ -3831,14 +3829,15 @@ fn complete_command_line(editor: &Editor, input: &str) -> Vec<ui::prompt::Comple
38313829
.get(command)
38323830
.map_or_else(Vec::new, |cmd| {
38333831
let args_offset = command.len() + 1;
3834-
complete_command_args(editor, cmd, rest, args_offset)
3832+
complete_command_args(editor, cmd.signature, &cmd.completer, rest, args_offset)
38353833
})
38363834
}
38373835
}
38383836

3839-
fn complete_command_args(
3837+
pub fn complete_command_args(
38403838
editor: &Editor,
3841-
command: &TypableCommand,
3839+
signature: Signature,
3840+
completer: &CommandCompleter,
38423841
input: &str,
38433842
offset: usize,
38443843
) -> Vec<ui::prompt::Completion> {
@@ -3850,7 +3849,7 @@ fn complete_command_args(
38503849
let cursor = input.len();
38513850
let prefix = &input[..cursor];
38523851
let mut tokenizer = Tokenizer::new(prefix, false);
3853-
let mut args = Args::new(command.signature, false);
3852+
let mut args = Args::new(signature, false);
38543853
let mut final_token = None;
38553854
let mut is_last_token = true;
38563855

@@ -3894,7 +3893,7 @@ fn complete_command_args(
38943893
.len()
38953894
.checked_sub(1)
38963895
.expect("completion state to be positional");
3897-
let completer = command.completer_for_argument_number(n);
3896+
let completer = completer.for_argument_number(n);
38983897

38993898
completer(editor, &token.content)
39003899
.into_iter()
@@ -3903,7 +3902,7 @@ fn complete_command_args(
39033902
}
39043903
CompletionState::Flag(_) => fuzzy_match(
39053904
token.content.trim_start_matches('-'),
3906-
command.signature.flags.iter().map(|flag| flag.name),
3905+
signature.flags.iter().map(|flag| flag.name),
39073906
false,
39083907
)
39093908
.into_iter()
@@ -3928,7 +3927,7 @@ fn complete_command_args(
39283927
.len()
39293928
.checked_sub(1)
39303929
.expect("completion state to be positional");
3931-
command.completer_for_argument_number(n)
3930+
completer.for_argument_number(n)
39323931
});
39333932
complete_expand(editor, &token, arg_completer, offset + token.content_start)
39343933
}

0 commit comments

Comments
 (0)