8000 bug #31377 [Console] Fix auto-complete for ChoiceQuestion (multi-sele… · symfony/symfony@60b505e · GitHub
[go: up one dir, main page]

Skip to content

Commit 60b505e

Browse files
committed
bug #31377 [Console] Fix auto-complete for ChoiceQuestion (multi-select answers) (battye)
This PR was squashed before being merged into the 3.4 branch (closes #31377). Discussion ---------- [Console] Fix auto-complete for ChoiceQuestion (multi-select answers) | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #24072 | License | MIT | Doc PR | - Previously, a bug existed whereby for multi-select questions, the auto-complete would only work for the first answer supplied by the user. On all subsequent answers, the auto-complete would not appear. Now it works as expected: ![screenshot](https://user-images.githubusercontent.com/2110222/57158657-8c147e80-6e16-11e9-94f7-a9bc95506545.png) Commits ------- 59321fe [Console] Fix auto-complete for ChoiceQuestion (multi-select answers)
2 parents 5453f3e + 59321fe commit 60b505e

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

src/Symfony/Component/Console/Helper/QuestionHelper.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ protected function writeError(OutputInterface $output, \Exception $error)
239239
*/
240240
private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
241241
{
242+
$fullChoice = '';
242243
$ret = '';
243244

244245
$i = 0;
@@ -265,6 +266,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
265266
} elseif ("\177" === $c) { // Backspace Character
266267
if (0 === $numMatches && 0 !== $i) {
267268
--$i;
269+
$fullChoice = substr($fullChoice, 0, -1);
268270
// Move cursor backwards
269271
$output->write("\033[1D");
270272
}
@@ -301,8 +303,10 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
301303
if ($numMatches > 0 && -1 !== $ofs) {
302304
$ret = $matches[$ofs];
303305
// Echo out remaining chars for current match
304-
$output->write(substr($ret, $i));
305-
$i = \strlen($ret);
306+
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
307+
$output->write($remainingCharacters);
308+
$fullChoice .= $remainingCharacters;
309+
$i = \strlen($fullChoice);
306310
}
307311

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

322326
$output->write($c);
323327
$ret .= $c;
328+
$fullChoice .= $c;
324329
++$i;
325330

331+
$tempRet = $ret;
332+
333+
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
334+
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
335+
}
336+
326337
$numMatches = 0;
327338
$ofs = 0;
328339

329340
foreach ($autocomplete as $value) {
330341
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
331-
if (0 === strpos($value, $ret)) {
342+
if (0 === strpos($value, $tempRet)) {
332343
$matches[$numMatches++] = $value;
333344
}
334345
}
@@ -340,8 +351,9 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
340351
if ($numMatches > 0 && -1 !== $ofs) {
341352
// Save cursor position
342353
$output->write("\0337");
343-
// Write highlighted text
344-
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
354+
// Write highlighted text, complete the partially entered response
355+
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
356+
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
345357
// Restore cursor position
346358
$output->write("\0338");
347359
}
@@ -350,7 +362,24 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
350362
// Reset stty so it behaves normally again
351363
shell_exec(sprintf('stty %s', $sttyMode));
352364

353-
return $ret;
365+
return $fullChoice;
366+
}
367+
368+
private function mostRecentlyEnteredValue($entered)
369+
{
370+
$tempEntered = $entered;
371+
372+
// Determine the most recent value that the user entered
373+
if (false !== strpos($entered, ',')) {
374+
$choices = explode(',', $entered);
375+
$lastChoice = trim($choices[\count($choices) - 1]);
376+
377+
if (\strlen($lastChoice) > 0) {
378+
$tempEntered = $lastChoice;
379+
}
380+
}
381+
382+
return $tempEntered;
354383
}
355384

356385
/**

src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,37 @@ public function testTraversableAutocomplete()
10181018
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
10191019
}
10201020

1021+
public function testTraversableMultiselectAutocomplete()
1022+
{
1023+
// <NEWLINE>
1024+
// F<TAB><NEWLINE>
1025+
// A<3x UP ARROW><TAB>,F<TAB><NEWLINE>
1026+
// F00<BACKSPACE><BACKSPACE>o<TAB>,A<DOWN ARROW>,<SPACE>SecurityBundle<NEWLINE>
1027+
// Acme<TAB>,<SPACE>As<TAB><29x BACKSPACE>S<TAB><NEWLINE>
1028+
// Ac<TAB>,As<TAB><3x BACKSPACE>d<TAB><NEWLINE>
1029+
$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");
1030+
1031+
$dialog = new QuestionHelper();
1032+
$helperSet = new HelperSet([new FormatterHelper()]);
1033+
$dialog->setHelperSet($helperSet);
1034+
1035+
$question = new ChoiceQuestion(
1036+
'Please select a bundle (defaults to AcmeDemoBundle and AsseticBundle)',
1037+
['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'],
1038+
'0,1'
1039+
);
1040+
1041+
// This tests that autocomplete works for all multiselect choices entered by the user
1042+
$question->setMultiselect(true);
1043+
1044+
$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1045+
$this->assertEquals(['FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1046+
$this->assertEquals(['AsseticBundle', 'FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1047+
$this->assertEquals(['FooBundle', 'AsseticBundle', 'SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1048+
$this->assertEquals(['SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1049+
$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1050+
}
1051+
10211052
protected function getInputStream($input)
10221053
{
10231054
$stream = fopen('php://memory', 'r+', false);

0 commit comments

Comments
 (0)
0