Skip to content

Commit

Permalink
merged branch lmcd/autocomplete-arrows-new (PR #6564)
Browse files Browse the repository at this point in the history
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. :+1:

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

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

@fabpot Added tests
  • Loading branch information
fabpot committed Jan 6, 2013
2 parents 416b95b + c5203e6 commit 77aa7a8
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 63 deletions.
126 changes: 65 additions & 61 deletions src/Symfony/Component/Console/Helper/DialogHelper.php
Expand Up @@ -86,10 +86,13 @@ public function ask(OutputInterface $output, $question, $default = null, array $
}
$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');

Expand All @@ -98,91 +101,92 @@ public function ask(OutputInterface $output, $question, $default = null, array $

// 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);
}

if ("\n" === $c) {
$output->write($c);
break;
}

$currentMatched = false;
// Did we read an escape sequence?
if ("\033" === $c) {
$c .= fread($inputStream, 2);

continue;
}
if ('A' === $c[2] || 'B' === $c[2]) {
if (0 === $i) {
$matches = $autocomplete;
$numMatches = count($matches);

if (ord($c) < 32) {
continue;
}

$output->write($c);
$ret .= $c;
$i++;
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
}

// Erase characters from cursor to end of line
$output->write("\033[K");
if (0 === $numMatches) {
continue;
}

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);
$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 ($ret === $matchTest) {
if ($i === strlen($value)) {
$currentMatched = false;
if ("\n" === $c) {
$output->write($c);
break;
}

// Save cursor position
$output->write("\0337");

$output->write('<hl>' . substr($value, $i) . '</hl>');

// Restore cursor position
$output->write("\0338");
$numMatches = 0;
}

$currentMatched = $j;
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;
}
}
}

// Erase characters from cursor to end of line
$output->write("\033[K");

$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");
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
Expand Up @@ -55,18 +55,24 @@ public function testAsk()
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();
}
Expand Down

0 comments on commit 77aa7a8

Please sign in to comment.