@@ -17,18 +17,18 @@ pub fn escape(input: &str) -> Cow<'_, str> {
20/// Get the vec of escaped / quoted / doublequoted filenames from the input str
30/// Get the vec of escaped / quoted / doublequoted filenames from the input str
21pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
31pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
32 use State::*;
32 use State::*;
34 let mut state = Unquoted;
34 let mut state = Unquoted;
@@ -140,6 +140,70 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
143+/// Checks that the input ends with an ascii whitespace character which is
149+/// use helix_core::shellwords::ends_with_whitespace;
150+/// assert_eq!(ends_with_whitespace(" "), true);
151+/// assert_eq!(ends_with_whitespace(":open "), true);
152+/// assert_eq!(ends_with_whitespace(":open foo.txt "), true);
153+/// assert_eq!(ends_with_whitespace(":open"), false);
155+/// assert_eq!(ends_with_whitespace(":open a\\ "), false);
157+/// assert_eq!(ends_with_whitespace(":open a\\ b.txt"), false);
159+pub fn ends_with_whitespace(input: &str) -> bool {
162+ // Fast-lane: the input must end with a whitespace character
163+ // regardless of quoting.
164+ if !input.ends_with(|c: char| c.is_ascii_whitespace()) {
168+ let mut state = Unquoted;
170+ for c in input.chars() {
171+ state = match state {
172+ OnWhitespace => match c {
175+ '\\' if cfg!(unix) => UnquotedEscaped,
176+ '\\' => OnWhitespace,
177+ c if c.is_ascii_whitespace() => OnWhitespace,
180+ Unquoted => match c {
181+ '\\' if cfg!(unix) => UnquotedEscaped,
183+ c if c.is_ascii_whitespace() => OnWhitespace,
186+ UnquotedEscaped => Unquoted,
188+ '\\' if cfg!(unix) => QuoteEscaped,
190+ '\'' => OnWhitespace,
193+ QuoteEscaped => Quoted,
194+ Dquoted => match c {
195+ '\\' if cfg!(unix) => DquoteEscaped,
197+ '"' => OnWhitespace,
200+ DquoteEscaped => Dquoted,
204+ matches!(state, OnWhitespace)
143#[cfg(test)]
207#[cfg(test)]
144mod test {
208mod test {
145 use super::*;
209 use super::*;