Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Console] Fix auto-complete for ChoiceQuestion (multi-select answers) #31377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions 41 src/Symfony/Component/Console/Helper/QuestionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ protected function writeError(OutputInterface $output, \Exception $error)
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
{
$fullChoice = '';
$ret = '';

$i = 0;
Expand All @@ -265,6 +266,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$fullChoice = substr($fullChoice, 0, -1);
// Move cursor backwards
$output->write("\033[1D");
}
Expand Down Expand Up @@ -301,8 +303,10 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
// Echo out remaining chars for current match
$output->write(substr($ret, $i));
$i = \strlen($ret);
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = \strlen($fullChoice);
}

if ("\n" === $c) {
Expand All @@ -321,14 +325,21 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu

$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;

$tempRet = $ret;

if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}

$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)) {
if (0 === strpos($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
Expand All @@ -340,8 +351,9 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output->write("\0337");
// Write highlighted text
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
// Restore cursor position
$output->write("\0338");
}
Expand All @@ -350,7 +362,24 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));

return $ret;
return $fullChoice;
}

private function mostRecentlyEnteredValue($entered)
{
$tempEntered = $entered;

// Determine the most recent value that the user entered
if (false !== strpos($entered, ',')) {
$choices = explode(',', $entered);
$lastChoice = trim($choices[\count($choices) - 1]);

if (\strlen($lastChoice) > 0) {
$tempEntered = $lastChoice;
}
}
chalasr marked this conversation as resolved.
Show resolved Hide resolved

return $tempEntered;
}

/**
Expand Down
31 changes: 31 additions & 0 deletions 31 src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,37 @@ public function testTraversableAutocomplete()
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}

public function testTraversableMultiselectAutocomplete()
{
// <NEWLINE>
// F<TAB><NEWLINE>
// A<3x UP ARROW><TAB>,F<TAB><NEWLINE>
// F00<BACKSPACE><BACKSPACE>o<TAB>,A<DOWN ARROW>,<SPACE>SecurityBundle<NEWLINE>
// Acme<TAB>,<SPACE>As<TAB><29x BACKSPACE>S<TAB><NEWLINE>
// Ac<TAB>,As<TAB><3x BACKSPACE>d<TAB><NEWLINE>
$inputStream = $this->getInputStream("\nF\t\nA\033[A\033[A\033[A\t,F\t\nF00\177\177o\t,A\033[B\t, SecurityBundle\nAcme\t, As\t\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177S\t\nAc\t,As\t\177\177\177d\t\n");

$dialog = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);

$question = new ChoiceQuestion(
'Please select a bundle (defaults to AcmeDemoBundle and AsseticBundle)',
['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'],
'0,1'
);

// This tests that autocomplete works for all multiselect choices entered by the user
$question->setMultiselect(true);

$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals(['FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals(['AsseticBundle', 'FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals(['FooBundle', 'AsseticBundle', 'SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals(['SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}

protected function getInputStream($input)
{
$stream = fopen('php://memory', 'r+', false);
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.