merged branch lmcd/autocomplete-arrows-new (PR #6564)

This PR was merged into the master branch.

Commits
-------

2b73975 Add new tests and fix problem with the second option being chosen on down arrow
8a0bcfb Use strpos in place of substr
9864d95 Not much use in blanking the array each time
a4d711c Enable arrow keys to browse matched autocomplete options

Discussion
----------

[Console] Enable arrow keys to browse matched autocomplete options

See notes in original autocomplete pull request: #6391
See also @bamarni's pull request implementing more or less the same stuff: #6561

---------------------------------------------------------------------------

by fabpot at 2013-01-05T10:12:47Z

Looks good to me. Can you add some unit tests?

---------------------------------------------------------------------------

by bamarni at 2013-01-05T12:51:51Z

1 line addition... you definitely got me on the diff!

That's what I had mind too, excepted for the default highlight, but as you said it's usually displayed in the question so it 's not necessary. 👍

---------------------------------------------------------------------------

by lmcd at 2013-01-06T04:07:02Z

@fabpot Added tests
This commit is contained in:
Fabien Potencier 2013-01-06 11:18:09 +01:00
commit 64bf80c2bb
2 changed files with 76 additions and 66 deletions

View File

@ -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('<hl>' . substr($value, $i) . '</hl>');
// 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('<hl>' . substr($matches[$ofs], $i) . '</hl>');
// Restore cursor position
$output->write("\0338");
}
}

View File

@ -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<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE>
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n");
// <UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB><NEWLINE>
// <DOWN ARROW><NEWLINE>
$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();
}