diff --git a/src/CompletionHandler.php b/src/CompletionHandler.php index 905ea5a..abd0b0b 100644 --- a/src/CompletionHandler.php +++ b/src/CompletionHandler.php @@ -34,6 +34,12 @@ class CompletionHandler */ protected $helpers = array(); + /** + * Index the command name was detected at + * @var int + */ + private $commandWordIndex; + public function __construct(Application $application, CompletionContext $context = null) { $this->application = $application; @@ -100,13 +106,8 @@ public function runCompletion() throw new \RuntimeException('A CompletionContext must be set before requesting completion.'); } - $cmdName = $this->getInput()->getFirstArgument(); - - try { - $this->command = $this->application->find($cmdName); - } catch (\InvalidArgumentException $e) { - // Exception thrown, when multiple or none commands are found. - } + // Set the command to query options and arugments from + $this->command = $this->detectCommand(); $process = array( 'completeForOptionValues', @@ -132,6 +133,9 @@ public function runCompletion() /** * Get an InputInterface representation of the completion context * + * @deprecated Incorrectly uses the ArrayInput API and is no longer needed. + * This will be removed in the next major version. + * * @return ArrayInput */ public function getInput() @@ -256,7 +260,7 @@ protected function completeForOptionValues() */ protected function completeForCommandName() { - if (!$this->command || (count($this->context->getWords()) == 2 && $this->context->getWordIndex() == 1)) { + if (!$this->command || $this->context->getWordIndex() == $this->commandWordIndex) { return $this->getCommandNames(); } @@ -362,7 +366,8 @@ protected function mapArgumentsToWords($argumentDefinitions) foreach ($this->context->getWords() as $wordIndex => $word) { // Skip program name, command name, options, and option values - if ($wordIndex < 2 + if ($wordIndex == 0 + || $wordIndex === $this->commandWordIndex || ($word && '-' === $word[0]) || in_array($previousWord, $optionsWithArgs)) { $previousWord = $word; @@ -469,4 +474,44 @@ protected function getCommandNames() return array_keys($commands); } } + + /** + * Find the current command name in the command-line + * + * Note this only cares about flag-type options. Options with values cannot + * appear before a command name in Symfony Console application. + * + * @return Command|null + */ + private function detectCommand() + { + // Always skip the first word (program name) + $skipNext = true; + + foreach ($this->context->getWords() as $index => $word) { + + // Skip word if flagged + if ($skipNext) { + $skipNext = false; + continue; + } + + // Skip empty words and words that look like options + if (strlen($word) == 0 || $word[0] === '-') { + continue; + } + + // Return the first unambiguous match to argument-like words + try { + $cmd = $this->application->find($word); + $this->commandWordIndex = $index; + return $cmd; + } catch (\InvalidArgumentException $e) { + // Exception thrown, when multiple or no commands are found. + } + } + + // No command found + return null; + } } diff --git a/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionHandlerTest.php b/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionHandlerTest.php index 9ae1599..f5b5935 100644 --- a/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionHandlerTest.php +++ b/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionHandlerTest.php @@ -68,6 +68,17 @@ public function testCompleteOptionShortcut() $this->assertEquals(array('-j'), $this->getTerms($handler->runCompletion())); } + public function testCompleteOptionShortcutFirst() + { + // Check command options complete + $handler = $this->createHandler('app -v wave --'); + $this->assertArraySubset(array('--vigorous', '--jazz-hands'), $this->getTerms($handler->runCompletion())); + + // Check unambiguous command name still completes + $handler = $this->createHandler('app --quiet wav'); + $this->assertEquals(array('wave'), $this->getTerms($handler->runCompletion())); + } + public function testCompleteDoubleDash() { $handler = $this->createHandler('app wave --');