-
-
Save stecman/fc8c44e3dd657cd845aebc9666e8877d to your computer and use it in GitHub Desktop.
symfony-console-completion string and escaping parsing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/CompletionContext.php b/src/CompletionContext.php | |
index 6029253..4316801 100644 | |
--- a/src/CompletionContext.php | |
+++ b/src/CompletionContext.php | |
@@ -198,20 +198,13 @@ class CompletionContext | |
$this->wordIndex = null; | |
$cursor = 0; | |
- $breaks = preg_quote($this->wordBreaks); | |
+ $matches = $this->splitOnBreaks($this->commandLine, $this->wordBreaks); | |
- if (!preg_match_all("/([^$breaks]*)([$breaks]*)/", $this->commandLine, $matches)) { | |
- return; | |
- } | |
- | |
- // Groups: | |
- // 1: Word | |
- // 2: Break characters | |
- foreach ($matches[0] as $index => $wholeMatch) { | |
+ foreach ($matches as $index => $match) { | |
// Determine which word the cursor is in | |
- $cursor += strlen($wholeMatch); | |
- $word = $matches[1][$index]; | |
- $breaks = $matches[2][$index]; | |
+ $word = $match['word']; | |
+ $breaks = $match['break']; | |
+ $cursor += strlen($word) + strlen($breaks); | |
if ($this->wordIndex === null && $cursor >= $this->charIndex) { | |
$this->wordIndex = $index; | |
@@ -246,6 +239,96 @@ class CompletionContext | |
} | |
/** | |
+ * Split a string into words on break characters, accounting for strings and escaped spaces | |
+ * | |
+ * Returns a collection of ['word' => string, 'break' => string] groups for each word, | |
+ * where "break" is the non-word characters between this word and then following one. | |
+ * | |
+ * @param string $string | |
+ * @param string $breaks | |
+ * @param string[] $wordGroupers | |
+ * @return array | |
+ */ | |
+ protected function splitOnBreaks($string, $breaks, $wordGroupers = ['\'', '"']) | |
+ { | |
+ $matches = []; | |
+ | |
+ $word = ''; | |
+ $break = ''; | |
+ | |
+ $currentGroup = null; | |
+ $escaping = false; | |
+ $inBreakSpace = false; | |
+ | |
+ for ($pos = 0, $length = strlen($string); $pos < $length; $pos++) { | |
+ $char = $string[$pos]; | |
+ $treatAsWord = false; | |
+ | |
+ // If we're escaping, we don't care what this character is - add it to the word | |
+ if ($escaping) { | |
+ $escaping = false; | |
+ $treatAsWord = true; | |
+ } | |
+ | |
+ // If a backslash is encountered, ignore any special function of the following character | |
+ else if ($char === '\\' && !$escaping) { | |
+ $escaping = true; | |
+ $treatAsWord = true; | |
+ } | |
+ | |
+ // Check for the start or end of a group | |
+ // We can only be inside one group at a time | |
+ else { | |
+ foreach ($wordGroupers as $groupChar) { | |
+ if ($char === $groupChar) { | |
+ // If we encouter the character that opened the current group, close the group | |
+ if ($currentGroup === $groupChar) { | |
+ $currentGroup = null; | |
+ break; | |
+ } | |
+ | |
+ // If we're not in a group yet, use this character as the group | |
+ if (!$currentGroup) { | |
+ $currentGroup = $groupChar; | |
+ break; | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ // If this is not a break character, add it to the word, otherwise add to the break space | |
+ // Being inside a group counts break characters as word characters | |
+ if ($treatAsWord || $currentGroup !== null || strpos($breaks, $char) === false) { | |
+ | |
+ // If the previous character was a break, this is a new word | |
+ if ($inBreakSpace) { | |
+ $matches[] = [ | |
+ 'word' => $word, | |
+ 'break' => $break | |
+ ]; | |
+ | |
+ $word = ''; | |
+ $break = ''; | |
+ $inBreakSpace = false; | |
+ } | |
+ | |
+ $word .= $char; | |
+ } else { | |
+ $inBreakSpace = true; | |
+ $break .= $char; | |
+ } | |
+ } | |
+ | |
+ // Append the last word | |
+ $matches[] = [ | |
+ 'word' => $word, | |
+ 'break' => $break | |
+ ]; | |
+ | |
+ return $matches; | |
+ } | |
+ | |
+ /** | |
* Reset the computed words so that $this->splitWords is forced to run again | |
*/ | |
protected function reset() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment