From 4ff5feeb0c6c1ce012aa1179a44f35b6f0df4da1 Mon Sep 17 00:00:00 2001
From: A-Walrus <58790821+A-Walrus@users.noreply.github.com>
Date: Fri, 21 Oct 2022 04:06:57 +0300
Subject: [PATCH] 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
---
 helix-core/src/shellwords.rs | 67 ++++++++++++++++++++++++++----------
 1 file changed, 49 insertions(+), 18 deletions(-)

diff --git a/helix-core/src/shellwords.rs b/helix-core/src/shellwords.rs
index 4323039a..afc83496 100644
--- a/helix-core/src/shellwords.rs
+++ b/helix-core/src/shellwords.rs
@@ -3,8 +3,9 @@ use std::borrow::Cow;
 /// Get the vec of escaped / quoted / doublequoted filenames from the input str
 pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
     enum State {
-        Normal,
-        NormalEscaped,
+        OnWhitespace,
+        Unquoted,
+        UnquotedEscaped,
         Quoted,
         QuoteEscaped,
         Dquoted,
@@ -13,7 +14,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
 
     use State::*;
 
-    let mut state = Normal;
+    let mut state = Unquoted;
     let mut args: Vec<Cow<str>> = Vec::new();
     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() {
         state = match state {
-            Normal => match c {
-                '\\' => {
-                    if cfg!(unix) {
-                        escaped.push_str(&input[start..i]);
-                        start = i + 1;
-                        NormalEscaped
-                    } else {
-                        Normal
-                    }
-                }
+            OnWhitespace => match c {
                 '"' => {
                     end = i;
                     Dquoted
@@ -40,13 +32,38 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
                     end = i;
                     Quoted
                 }
+                '\\' => {
+                    if cfg!(unix) {
+                        escaped.push_str(&input[start..i]);
+                        start = i + 1;
+                        UnquotedEscaped
+                    } else {
+                        OnWhitespace
+                    }
+                }
                 c if c.is_ascii_whitespace() => {
                     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 {
                 '\\' => {
                     if cfg!(unix) {
@@ -59,7 +76,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
                 }
                 '\'' => {
                     end = i;
-                    Normal
+                    OnWhitespace
                 }
                 _ => Quoted,
             },
@@ -76,7 +93,7 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
                 }
                 '"' => {
                     end = i;
-                    Normal
+                    OnWhitespace
                 }
                 _ => Dquoted,
             },
@@ -195,4 +212,18 @@ mod test {
         ];
         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);
+    }
 }