aboutsummaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core')
-rw-r--r--helix-core/src/lib.rs1
-rw-r--r--helix-core/src/shellwords.rs164
2 files changed, 165 insertions, 0 deletions
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 4ae044cc..92a59f31 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -17,6 +17,7 @@ mod position;
pub mod register;
pub mod search;
pub mod selection;
+pub mod shellwords;
mod state;
pub mod surround;
pub mod syntax;
diff --git a/helix-core/src/shellwords.rs b/helix-core/src/shellwords.rs
new file mode 100644
index 00000000..13f6f3e9
--- /dev/null
+++ b/helix-core/src/shellwords.rs
@@ -0,0 +1,164 @@
+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,
+ Quoted,
+ QuoteEscaped,
+ Dquoted,
+ DquoteEscaped,
+ }
+
+ use State::*;
+
+ let mut state = Normal;
+ let mut args: Vec<Cow<str>> = Vec::new();
+ let mut escaped = String::with_capacity(input.len());
+
+ let mut start = 0;
+ let mut end = 0;
+
+ for (i, c) in input.char_indices() {
+ state = match state {
+ Normal => match c {
+ '\\' => {
+ escaped.push_str(&input[start..i]);
+ start = i + 1;
+ NormalEscaped
+ }
+ '"' => {
+ end = i;
+ Dquoted
+ }
+ '\'' => {
+ end = i;
+ Quoted
+ }
+ c if c.is_ascii_whitespace() => {
+ end = i;
+ Normal
+ }
+ _ => Normal,
+ },
+ NormalEscaped => Normal,
+ Quoted => match c {
+ '\\' => {
+ escaped.push_str(&input[start..i]);
+ start = i + 1;
+ QuoteEscaped
+ }
+ '\'' => {
+ end = i;
+ Normal
+ }
+ _ => Quoted,
+ },
+ QuoteEscaped => Quoted,
+ Dquoted => match c {
+ '\\' => {
+ escaped.push_str(&input[start..i]);
+ start = i + 1;
+ DquoteEscaped
+ }
+ '"' => {
+ end = i;
+ Normal
+ }
+ _ => Dquoted,
+ },
+ DquoteEscaped => Dquoted,
+ };
+
+ if i >= input.len() - 1 && end == 0 {
+ end = i + 1;
+ }
+
+ if end > 0 {
+ let esc_trim = escaped.trim();
+ let inp = &input[start..end];
+
+ if !(esc_trim.is_empty() && inp.trim().is_empty()) {
+ if esc_trim.is_empty() {
+ args.push(inp.into());
+ } else {
+ args.push([escaped, inp.into()].concat().into());
+ escaped = "".to_string();
+ }
+ }
+ start = i + 1;
+ end = 0;
+ }
+ }
+ args
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_normal() {
+ let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
+ let result = shellwords(input);
+ let expected = vec![
+ Cow::from(":o"),
+ Cow::from("single_word"),
+ Cow::from("twó"),
+ Cow::from("wörds"),
+ Cow::from(r#"three "with escaping\"#),
+ ];
+ // TODO test is_owned and is_borrowed, once they get stabilized.
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_quoted() {
+ let quoted =
+ r#":o 'single_word' 'twó wörds' '' ' ''\three\' \"with\ escaping\\' 'quote incomplete"#;
+ let result = shellwords(quoted);
+ let expected = vec![
+ Cow::from(":o"),
+ Cow::from("single_word"),
+ Cow::from("twó wörds"),
+ Cow::from(r#"three' "with escaping\"#),
+ Cow::from("quote incomplete"),
+ ];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_dquoted() {
+ let dquoted = r#":o "single_word" "twó wörds" "" " ""\three\' \"with\ escaping\\" "dquote incomplete"#;
+ let result = shellwords(dquoted);
+ let expected = vec![
+ Cow::from(":o"),
+ Cow::from("single_word"),
+ Cow::from("twó wörds"),
+ Cow::from(r#"three' "with escaping\"#),
+ Cow::from("dquote incomplete"),
+ ];
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_mixed() {
+ let dquoted = r#":o single_word 'twó wörds' "\three\' \"with\ escaping\\""no space before"'and after' $#%^@ "%^&(%^" ')(*&^%''a\\\\\b' '"#;
+ let result = shellwords(dquoted);
+ let expected = vec![
+ Cow::from(":o"),
+ Cow::from("single_word"),
+ Cow::from("twó wörds"),
+ Cow::from("three' \"with escaping\\"),
+ Cow::from("no space before"),
+ Cow::from("and after"),
+ Cow::from("$#%^@"),
+ Cow::from("%^&(%^"),
+ Cow::from(")(*&^%"),
+ Cow::from(r#"a\\b"#),
+ //last ' just changes to quoted but since we dont have anything after it, it should be ignored
+ ];
+ assert_eq!(expected, result);
+ }
+}