aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/shellwords.rs
diff options
context:
space:
mode:
authorMichael Davis2022-11-04 01:55:13 +0000
committerBlaž Hrastnik2022-11-07 04:38:16 +0000
commit1536a6528968f38adfac2e991b29006f5ded5968 (patch)
tree73b1b0ebe81b03e03af6e8098c7c92259b728d03 /helix-core/src/shellwords.rs
parent48a3965ab43718ce2a49724cbcc294b04c328b81 (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/src/shellwords.rs')
-rw-r--r--helix-core/src/shellwords.rs84
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::*;