Unnamed repository; edit this file 'description' to name the repository.
Fix shell pipe command lines not using expansions (#14191)
| -rw-r--r-- | helix-term/src/commands.rs | 98 | ||||
| -rw-r--r-- | helix-term/src/commands/typed.rs | 35 |
2 files changed, 66 insertions, 67 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 18f24a63..8edf5944 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -22,7 +22,8 @@ pub use typed::*; use helix_core::{ char_idx_at_visual_offset, chars::char_is_word, - command_line, comment, + command_line::{self, Args}, + comment, doc_formatter::TextFormat, encoding, find_workspace, graphemes::{self, next_grapheme_boundary}, @@ -46,6 +47,7 @@ use helix_core::{ use helix_view::{ document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, editor::Action, + expansion, info::Info, input::KeyEvent, keyboard::KeyCode, @@ -6256,64 +6258,52 @@ enum ShellBehavior { } fn shell_pipe(cx: &mut Context) { - shell_prompt(cx, "pipe:".into(), ShellBehavior::Replace); + shell_prompt_for_behavior(cx, "pipe:".into(), ShellBehavior::Replace); } fn shell_pipe_to(cx: &mut Context) { - shell_prompt(cx, "pipe-to:".into(), ShellBehavior::Ignore); + shell_prompt_for_behavior(cx, "pipe-to:".into(), ShellBehavior::Ignore); } fn shell_insert_output(cx: &mut Context) { - shell_prompt(cx, "insert-output:".into(), ShellBehavior::Insert); + shell_prompt_for_behavior(cx, "insert-output:".into(), ShellBehavior::Insert); } fn shell_append_output(cx: &mut Context) { - shell_prompt(cx, "append-output:".into(), ShellBehavior::Append); + shell_prompt_for_behavior(cx, "append-output:".into(), ShellBehavior::Append); } fn shell_keep_pipe(cx: &mut Context) { - ui::prompt( - cx, - "keep-pipe:".into(), - Some('|'), - ui::completers::none, - move |cx, input: &str, event: PromptEvent| { - let shell = &cx.editor.config().shell; - if event != PromptEvent::Validate { - return; - } - if input.is_empty() { - return; - } - let (view, doc) = current!(cx.editor); - let selection = doc.selection(view.id); + shell_prompt(cx, "keep-pipe:".into(), |cx, args| { + let shell = &cx.editor.config().shell; + let (view, doc) = current!(cx.editor); + let selection = doc.selection(view.id); - let mut ranges = SmallVec::with_capacity(selection.len()); - let old_index = selection.primary_index(); - let mut index: Option<usize> = None; - let text = doc.text().slice(..); + let mut ranges = SmallVec::with_capacity(selection.len()); + let old_index = selection.primary_index(); + let mut index: Option<usize> = None; + let text = doc.text().slice(..); - for (i, range) in selection.ranges().iter().enumerate() { - let fragment = range.slice(text); - if let Err(err) = shell_impl(shell, input, Some(fragment.into())) { - log::debug!("Shell command failed: {}", err); - } else { - ranges.push(*range); - if i >= old_index && index.is_none() { - index = Some(ranges.len() - 1); - } + for (i, range) in selection.ranges().iter().enumerate() { + let fragment = range.slice(text); + if let Err(err) = shell_impl(shell, args.join(" ").as_str(), Some(fragment.into())) { + log::debug!("Shell command failed: {}", err); + } else { + ranges.push(*range); + if i >= old_index && index.is_none() { + index = Some(ranges.len() - 1); } } + } - if ranges.is_empty() { - cx.editor.set_error("No selections remaining"); - return; - } + if ranges.is_empty() { + cx.editor.set_error("No selections remaining"); + return; + } - let index = index.unwrap_or_else(|| ranges.len() - 1); - doc.set_selection(view.id, Selection::new(ranges, index)); - }, - ); + let index = index.unwrap_or_else(|| ranges.len() - 1); + doc.set_selection(view.id, Selection::new(ranges, index)); + }); } 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) { view.ensure_cursor_in_view(doc, config.scrolloff); } -fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { +fn shell_prompt<F>(cx: &mut Context, prompt: Cow<'static, str>, mut callback_fn: F) +where + F: FnMut(&mut compositor::Context, Args) + 'static, +{ ui::prompt( cx, prompt, Some('|'), - ui::completers::shell, - move |cx, input: &str, event: PromptEvent| { - if event != PromptEvent::Validate { + |editor, input| complete_command_args(editor, SHELL_SIGNATURE, &SHELL_COMPLETER, input, 0), + move |cx, input, event| { + if event != PromptEvent::Validate || input.is_empty() { return; } - if input.is_empty() { - return; + match Args::parse(input, SHELL_SIGNATURE, true, |token| { + expansion::expand(cx.editor, token).map_err(|err| err.into()) + }) { + Ok(args) => callback_fn(cx, args), + Err(err) => cx.editor.set_error(err.to_string()), } - - shell(cx, input, &behavior); }, ); } +fn shell_prompt_for_behavior(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { + shell_prompt(cx, prompt, move |cx, args| { + shell(cx, args.join(" ").as_str(), &behavior) + }) +} + fn suspend(_cx: &mut Context) { #[cfg(not(windows))] { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 263d337a..d26b4705 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -29,15 +29,6 @@ pub struct TypableCommand { pub signature: Signature, } -impl TypableCommand { - fn completer_for_argument_number(&self, n: usize) -> &Completer { - match self.completer.positional_args.get(n) { - Some(completer) => completer, - _ => &self.completer.var_args, - } - } -} - #[derive(Clone)] pub struct CommandCompleter { // Arguments with specific completion methods based on their position. @@ -68,6 +59,13 @@ impl CommandCompleter { var_args: completer, } } + + fn for_argument_number(&self, n: usize) -> &Completer { + match self.positional_args.get(n) { + Some(completer) => completer, + _ => &self.var_args, + } + } } fn quit(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> { @@ -2658,13 +2656,13 @@ const BUFFER_CLOSE_OTHERS_SIGNATURE: Signature = Signature { // but Signature does not yet allow for var args. /// This command handles all of its input as-is with no quoting or flags. -const SHELL_SIGNATURE: Signature = Signature { +pub const SHELL_SIGNATURE: Signature = Signature { positionals: (1, Some(2)), raw_after: Some(1), ..Signature::DEFAULT }; -const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[ +pub const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[ // Command name completers::program, // Shell argument(s) @@ -3831,14 +3829,15 @@ fn complete_command_line(editor: &Editor, input: &str) -> Vec<ui::prompt::Comple .get(command) .map_or_else(Vec::new, |cmd| { let args_offset = command.len() + 1; - complete_command_args(editor, cmd, rest, args_offset) + complete_command_args(editor, cmd.signature, &cmd.completer, rest, args_offset) }) } } -fn complete_command_args( +pub fn complete_command_args( editor: &Editor, - command: &TypableCommand, + signature: Signature, + completer: &CommandCompleter, input: &str, offset: usize, ) -> Vec<ui::prompt::Completion> { @@ -3850,7 +3849,7 @@ fn complete_command_args( let cursor = input.len(); let prefix = &input[..cursor]; let mut tokenizer = Tokenizer::new(prefix, false); - let mut args = Args::new(command.signature, false); + let mut args = Args::new(signature, false); let mut final_token = None; let mut is_last_token = true; @@ -3894,7 +3893,7 @@ fn complete_command_args( .len() .checked_sub(1) .expect("completion state to be positional"); - let completer = command.completer_for_argument_number(n); + let completer = completer.for_argument_number(n); completer(editor, &token.content) .into_iter() @@ -3903,7 +3902,7 @@ fn complete_command_args( } CompletionState::Flag(_) => fuzzy_match( token.content.trim_start_matches('-'), - command.signature.flags.iter().map(|flag| flag.name), + signature.flags.iter().map(|flag| flag.name), false, ) .into_iter() @@ -3928,7 +3927,7 @@ fn complete_command_args( .len() .checked_sub(1) .expect("completion state to be positional"); - command.completer_for_argument_number(n) + completer.for_argument_number(n) }); complete_expand(editor, &token, arg_completer, offset + token.content_start) } |