Fix shellwords delimiter handling (#4098)

* Fix shellwords delimiter handling

This allows commands such as `:set statusline.center ["file-type"]` to
work. Before the quotes within the list would mess it up.
Also added a test to ensure correct behavior

* Rename Delimiter -> OnWhitespace
This commit is contained in:
A-Walrus 2022-10-21 04:06:57 +03:00 committed by GitHub
parent 9af7c1c9f3
commit 4ff5feeb0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -3,8 +3,9 @@ use std::borrow::Cow;
/// Get the vec of escaped / quoted / doublequoted filenames from the input str /// Get the vec of escaped / quoted / doublequoted filenames from the input str
pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> { pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
enum State { enum State {
Normal, OnWhitespace,
NormalEscaped, Unquoted,
UnquotedEscaped,
Quoted, Quoted,
QuoteEscaped, QuoteEscaped,
Dquoted, Dquoted,
@ -13,7 +14,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
use State::*; use State::*;
let mut state = Normal; let mut state = Unquoted;
let mut args: Vec<Cow<str>> = Vec::new(); let mut args: Vec<Cow<str>> = Vec::new();
let mut escaped = String::with_capacity(input.len()); let mut escaped = String::with_capacity(input.len());
@ -22,16 +23,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
for (i, c) in input.char_indices() { for (i, c) in input.char_indices() {
state = match state { state = match state {
Normal => match c { OnWhitespace => match c {
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[start..i]);
start = i + 1;
NormalEscaped
} else {
Normal
}
}
'"' => { '"' => {
end = i; end = i;
Dquoted Dquoted
@ -40,13 +32,38 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
end = i; end = i;
Quoted Quoted
} }
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[start..i]);
start = i + 1;
UnquotedEscaped
} else {
OnWhitespace
}
}
c if c.is_ascii_whitespace() => { c if c.is_ascii_whitespace() => {
end = i; end = i;
Normal OnWhitespace
} }
_ => Normal, _ => Unquoted,
}, },
NormalEscaped => Normal, Unquoted => match c {
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[start..i]);
start = i + 1;
UnquotedEscaped
} else {
Unquoted
}
}
c if c.is_ascii_whitespace() => {
end = i;
OnWhitespace
}
_ => Unquoted,
},
UnquotedEscaped => Unquoted,
Quoted => match c { Quoted => match c {
'\\' => { '\\' => {
if cfg!(unix) { if cfg!(unix) {
@ -59,7 +76,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
} }
'\'' => { '\'' => {
end = i; end = i;
Normal OnWhitespace
} }
_ => Quoted, _ => Quoted,
}, },
@ -76,7 +93,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
} }
'"' => { '"' => {
end = i; end = i;
Normal OnWhitespace
} }
_ => Dquoted, _ => Dquoted,
}, },
@ -195,4 +212,18 @@ mod test {
]; ];
assert_eq!(expected, result); assert_eq!(expected, result);
} }
#[test]
fn test_lists() {
let input =
r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "qoutes"]'"#;
let result = shellwords(input);
let expected = vec![
Cow::from(":set"),
Cow::from("statusline.center"),
Cow::from(r#"["file-type","file-encoding"]"#),
Cow::from(r#"["list", "in", "qoutes"]"#),
];
assert_eq!(expected, result);
}
} }