diff --git a/helix-core/src/shellwords.rs b/helix-core/src/shellwords.rs index 0a8689a8b..ca761e0d7 100644 --- a/helix-core/src/shellwords.rs +++ b/helix-core/src/shellwords.rs @@ -40,7 +40,6 @@ pub struct Shellwords<'a> { impl<'a> From<&'a str> for Shellwords<'a> { fn from(input: &'a str) -> Self { use State::*; - let mut state = Unquoted; let mut words = Vec::new(); let mut parts = Vec::new(); @@ -52,30 +51,27 @@ impl<'a> From<&'a str> for Shellwords<'a> { let mut end = 0; for (i, c) in input.char_indices() { - if !inside_variable_expansion { - if c == '%' { - //%sh{this "should" be escaped} - if let Some(t) = input.get(i + 1..i + 3) { - if t == "sh" { - nested_variable_expansion_count += 1; - inside_variable_expansion = true; - } + if c == '%' { + //%sh{this "should" be escaped} + if let Some(t) = input.get(i + 1..i + 3) { + if t == "sh" { + nested_variable_expansion_count += 1; + inside_variable_expansion = true; } - //%{this "should" be escaped} - if let Some(t) = input.get(i + 1..i + 2) { - if t == "{" { - nested_variable_expansion_count += 1; - inside_variable_expansion = true; - } + } + //%{this "should" be escaped} + if let Some(t) = input.get(i + 1..i + 2) { + if t == "{" { + nested_variable_expansion_count += 1; + inside_variable_expansion = true; } } - } else if c == '}' { + } + if c == '}' { nested_variable_expansion_count -= 1; if nested_variable_expansion_count == 0 { inside_variable_expansion = false; } - } else if c == '{' { - nested_variable_expansion_count += 1; } state = if !inside_variable_expansion { @@ -266,6 +262,38 @@ mod test { // TODO test is_owned and is_borrowed, once they get stabilized. assert_eq!(expected, result); } + #[test] + fn test_expansion() { + let input = r#"echo %{filename} %{linenumber}"#; + let shellwords = Shellwords::from(input); + let result = shellwords.words().to_vec(); + let expected = vec![ + Cow::from("echo"), + Cow::from("%{filename}"), + Cow::from("%{linenumber}"), + ]; + assert_eq!(expected, result); + + let input = r#"echo %{filename} 'world' %{something to 'escape}"#; + let shellwords = Shellwords::from(input); + let result = shellwords.words().to_vec(); + let expected = vec![ + Cow::from("echo"), + Cow::from("%{filename}"), + Cow::from("world"), + Cow::from("%{something to 'escape}"), + ]; + assert_eq!(expected, result); + let input = r#"echo %sh{%sh{%{filename}}} cool"#; + let shellwords = Shellwords::from(input); + let result = shellwords.words().to_vec(); + let expected = vec![ + Cow::from("echo"), + Cow::from("%sh{%sh{%{filename}}}"), + Cow::from("cool"), + ]; + assert_eq!(expected, result); + } #[test] #[cfg(unix)] diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 0ee03705f..661c10560 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -217,22 +217,25 @@ impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { Self::Typable { name, args, doc: _ } => { - let args: Vec> = args.iter().map(Cow::from).collect(); - if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { - let mut cx = compositor::Context { - editor: cx.editor, - jobs: cx.jobs, - scroll: None, - }; - - match cx.editor.expand_variables(&args) { - Ok(args) => { - if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) - { - cx.editor.set_error(format!("{}", e)); - } + let mut args: Vec> = args.iter().map(Cow::from).collect(); + let joined_args = vec![Cow::from(args.join(" "))]; + if let Ok(expanded_args) = cx.editor.expand_variables(&joined_args) { + args = expanded_args + .first() + .unwrap() + .split(' ') + .map(Cow::from) + .collect(); + if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { + let mut cx = compositor::Context { + editor: cx.editor, + jobs: cx.jobs, + scroll: None, + }; + + if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) { + cx.editor.set_error(format!("{}", e)); } - Err(err) => cx.editor.set_error(err.to_string()), } } }