diff options
author | Michael Davis | 2022-11-04 01:55:13 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2022-11-07 04:38:16 +0000 |
commit | 1536a6528968f38adfac2e991b29006f5ded5968 (patch) | |
tree | 73b1b0ebe81b03e03af6e8098c7c92259b728d03 /helix-core | |
parent | 48a3965ab43718ce2a49724cbcc294b04c328b81 (diff) |
Fix whitespace handling in command-mode completion
8584b38cfbe6ffe3e5d539ad953c413e44e90bfa switched to shellwords for
completion in command-mode. This changes the conditions for choosing
whether to complete the command or use the command's completer.
This change processes the input as shellwords up-front and uses
shellword logic about whitespace to determine whether the command
or argument should be completed.
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/shellwords.rs | 84 |
1 files changed, 74 insertions, 10 deletions
diff --git a/helix-core/src/shellwords.rs b/helix-core/src/shellwords.rs index e8c5945b..3375bef1 100644 --- a/helix-core/src/shellwords.rs +++ b/helix-core/src/shellwords.rs @@ -17,18 +17,18 @@ pub fn escape(input: &str) -> Cow<'_, str> { } } +enum State { + OnWhitespace, + Unquoted, + UnquotedEscaped, + Quoted, + QuoteEscaped, + Dquoted, + DquoteEscaped, +} + /// Get the vec of escaped / quoted / doublequoted filenames from the input str pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> { - enum State { - OnWhitespace, - Unquoted, - UnquotedEscaped, - Quoted, - QuoteEscaped, - Dquoted, - DquoteEscaped, - } - use State::*; let mut state = Unquoted; @@ -140,6 +140,70 @@ pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> { args } +/// Checks that the input ends with an ascii whitespace character which is +/// not escaped. +/// +/// # Examples +/// +/// ```rust +/// use helix_core::shellwords::ends_with_whitespace; +/// assert_eq!(ends_with_whitespace(" "), true); +/// assert_eq!(ends_with_whitespace(":open "), true); +/// assert_eq!(ends_with_whitespace(":open foo.txt "), true); +/// assert_eq!(ends_with_whitespace(":open"), false); +/// #[cfg(unix)] +/// assert_eq!(ends_with_whitespace(":open a\\ "), false); +/// #[cfg(unix)] +/// assert_eq!(ends_with_whitespace(":open a\\ b.txt"), false); +/// ``` +pub fn ends_with_whitespace(input: &str) -> bool { + use State::*; + + // Fast-lane: the input must end with a whitespace character + // regardless of quoting. + if !input.ends_with(|c: char| c.is_ascii_whitespace()) { + return false; + } + + let mut state = Unquoted; + + for c in input.chars() { + state = match state { + OnWhitespace => match c { + '"' => Dquoted, + '\'' => Quoted, + '\\' if cfg!(unix) => UnquotedEscaped, + '\\' => OnWhitespace, + c if c.is_ascii_whitespace() => OnWhitespace, + _ => Unquoted, + }, + Unquoted => match c { + '\\' if cfg!(unix) => UnquotedEscaped, + '\\' => Unquoted, + c if c.is_ascii_whitespace() => OnWhitespace, + _ => Unquoted, + }, + UnquotedEscaped => Unquoted, + Quoted => match c { + '\\' if cfg!(unix) => QuoteEscaped, + '\\' => Quoted, + '\'' => OnWhitespace, + _ => Quoted, + }, + QuoteEscaped => Quoted, + Dquoted => match c { + '\\' if cfg!(unix) => DquoteEscaped, + '\\' => Dquoted, + '"' => OnWhitespace, + _ => Dquoted, + }, + DquoteEscaped => Dquoted, + } + } + + matches!(state, OnWhitespace) +} + #[cfg(test)] mod test { use super::*; |