diff --git a/src/Symfony/Component/Console/Helper/DialogHelper.php b/src/Symfony/Component/Console/Helper/DialogHelper.php index 7e6a51ed26..87c9026a31 100644 --- a/src/Symfony/Component/Console/Helper/DialogHelper.php +++ b/src/Symfony/Component/Console/Helper/DialogHelper.php @@ -86,10 +86,13 @@ class DialogHelper extends Helper } $ret = trim($ret); } else { - $i = 0; - $currentMatched = false; $ret = ''; + $i = 0; + $matches = array(); + $numMatches = 0; + $ofs = -1; + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); @@ -98,91 +101,92 @@ class DialogHelper extends Helper // Read a keypress while ($c = fread($inputStream, 1)) { - // Did we read an escape sequence? - if ("\033" === $c) { - $c .= fread($inputStream, 2); - - // Escape sequences for arrow keys - if ('A' === $c[2] || 'B' === $c[2] || 'C' === $c[2] || 'D' === $c[2]) { - // todo - } - - continue; - } - // Backspace Character if ("\177" === $c) { - if ($i === 0) { - continue; - } - - if (false === $currentMatched) { + if (0 === $numMatches && 0 !== $i) { $i--; // Move cursor backwards $output->write("\033[1D"); } + if ($i === 0) { + $ofs = -1; + } + // Erase characters from cursor to end of line $output->write("\033[K"); $ret = substr($ret, 0, $i); - $currentMatched = false; + $numMatches = 0; continue; } - if ("\t" === $c || "\n" === $c) { - if (false !== $currentMatched) { - // Echo out completed match - $output->write(substr($autocomplete[$currentMatched], strlen($ret))); - $ret = $autocomplete[$currentMatched]; - $i = strlen($ret); + // Did we read an escape sequence? + if ("\033" === $c) { + $c .= fread($inputStream, 2); + + if ('A' === $c[2] || 'B' === $c[2]) { + if (0 === $i) { + $matches = $autocomplete; + $numMatches = count($matches); + + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } else if (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0) { + $ret = $matches[$ofs]; + // Echo out completed match + $output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; } - if ("\n" === $c) { - $output->write($c); - break; + continue; + } else { + $output->write($c); + $ret .= $c; + $i++; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } } - - $currentMatched = false; - - continue; } - if (ord($c) < 32) { - continue; - } - - $output->write($c); - $ret .= $c; - $i++; - // Erase characters from cursor to end of line $output->write("\033[K"); - foreach ($autocomplete as $j => $value) { - // Get a substring of the current autocomplete item based on number of chars typed (e.g. AcmeDemoBundle = Acme) - $matchTest = substr($value, 0, $i); - - if ($ret === $matchTest) { - if ($i === strlen($value)) { - $currentMatched = false; - break; - } - - // Save cursor position - $output->write("\0337"); - - $output->write('' . substr($value, $i) . ''); - - // Restore cursor position - $output->write("\0338"); - - $currentMatched = $j; - break; - } - - $currentMatched = false; + if ($numMatches > 0) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write('' . substr($matches[$ofs], $i) . ''); + // Restore cursor position + $output->write("\0338"); } } diff --git a/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php index 87b47cdfe1..7f91f2225d 100644 --- a/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php @@ -55,18 +55,24 @@ class DialogHelperTest extends \PHPUnit_Framework_TestCase rewind($output->getStream()); $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); - $bundles = array('AcmeDemoBundle', 'AsseticBundle'); + $bundles = array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'); // Acm // AcsTest // - $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n"); + // + // + // + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\n"); $dialog->setInputStream($inputStream); if ($this->hasSttyAvailable()) { $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); $this->assertEquals('AsseticBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); $this->assertEquals('FrameworkBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); } else { $this->markTestSkipped(); }