Skip to content

Commit b15836b

Browse files
committed
Merge branch '4.2'
* 4.2: [Console] Fix auto-complete for ChoiceQuestion (multi-select answers) Translated form, security, validators resources into Belarusian (be) [WebProfilerBundle] Don't filter submitted IP values [Intl] Cleanup bumped Symfony version to 4.2.9 updated VERSION for 4.2.8 updated CHANGELOG for 4.2.8 bumped Symfony version to 3.4.28 updated VERSION for 3.4.27 update CONTRIBUTORS for 3.4.27 updated CHANGELOG for 3.4.27
2 parents 47b7895 + c381423 commit b15836b

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

Helper/QuestionHelper.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ protected function writeError(OutputInterface $output, \Exception $error)
198198
*/
199199
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
200200
{
201+
$fullChoice = '';
201202
$ret = '';
202203

203204
$i = 0;
@@ -224,6 +225,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
224225
} elseif ("\177" === $c) { // Backspace Character
225226
if (0 === $numMatches && 0 !== $i) {
226227
--$i;
228+
$fullChoice = substr($fullChoice, 0, -1);
227229
// Move cursor backwards
228230
$output->write("\033[1D");
229231
}
@@ -260,8 +262,10 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
260262
if ($numMatches > 0 && -1 !== $ofs) {
261263
$ret = (string) $matches[$ofs];
262264
// Echo out remaining chars for current match
263-
$output->write(substr($ret, $i));
264-
$i = \strlen($ret);
265+
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
266+
$output->write($remainingCharacters);
267+
$fullChoice .= $remainingCharacters;
268+
$i = \strlen($fullChoice);
265269

266270
$matches = array_filter(
267271
$autocomplete($ret),
@@ -287,14 +291,21 @@ function ($match) use ($ret) {
287291

288292
$output->write($c);
289293
$ret .= $c;
294+
$fullChoice .= $c;
290295
++$i;
291296

297+
$tempRet = $ret;
298+
299+
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
300+
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
301+
}
302+
292303
$numMatches = 0;
293304
$ofs = 0;
294305

295306
foreach ($autocomplete($ret) as $value) {
296307
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
297-
if (0 === strpos($value, $ret)) {
308+
if (0 === strpos($value, $tempRet)) {
298309
$matches[$numMatches++] = $value;
299310
}
300311
}
@@ -306,8 +317,9 @@ function ($match) use ($ret) {
306317
if ($numMatches > 0 && -1 !== $ofs) {
307318
// Save cursor position
308319
$output->write("\0337");
309-
// Write highlighted text
310-
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
320+
// Write highlighted text, complete the partially entered response
321+
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
322+
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
311323
// Restore cursor position
312324
$output->write("\0338");
313325
}
@@ -316,7 +328,24 @@ function ($match) use ($ret) {
316328
// Reset stty so it behaves normally again
317329
shell_exec(sprintf('stty %s', $sttyMode));
318330

319-
return $ret;
331+
return $fullChoice;
332+
}
333+
334+
private function mostRecentlyEnteredValue($entered)
335+
{
336+
$tempEntered = $entered;
337+
338+
// Determine the most recent value that the user entered
339+
if (false !== strpos($entered, ',')) {
340+
$choices = explode(',', $entered);
341+
$lastChoice = trim($choices[\count($choices) - 1]);
342+
343+
if (\strlen($lastChoice) > 0) {
344+
$tempEntered = $lastChoice;
345+
}
346+
}
347+
348+
return $tempEntered;
320349
}
321350

322351
/**

Tests/Helper/QuestionHelperTest.php

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

731+
public function testTraversableMultiselectAutocomplete()
732+
{
733+
// <NEWLINE>
734+
// F<TAB><NEWLINE>
735+
// A<3x UP ARROW><TAB>,F<TAB><NEWLINE>
736+
// F00<BACKSPACE><BACKSPACE>o<TAB>,A<DOWN ARROW>,<SPACE>SecurityBundle<NEWLINE>
737+
// Acme<TAB>,<SPACE>As<TAB><29x BACKSPACE>S<TAB><NEWLINE>
738+
// Ac<TAB>,As<TAB><3x BACKSPACE>d<TAB><NEWLINE>
739+
$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");
740+
741+
$dialog = new QuestionHelper();
742+
$helperSet = new HelperSet([new FormatterHelper()]);
743+
$dialog->setHelperSet($helperSet);
744+
745+
$question = new ChoiceQuestion(
746+
'Please select a bundle (defaults to AcmeDemoBundle and AsseticBundle)',
747+
['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'],
748+
'0,1'
749+
);
750+
751+
// This tests that autocomplete works for all multiselect choices entered by the user
752+
$question->setMultiselect(true);
753+
754+
$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
755+
$this->assertEquals(['FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
756+
$this->assertEquals(['AsseticBundle', 'FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
757+
$this->assertEquals(['FooBundle', 'AsseticBundle', 'SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
758+
$this->assertEquals(['SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
759+
$this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
760+
}
761+
731762
protected function getInputStream($input)
732763
{
733764
$stream = fopen('php://memory', 'r+', false);

0 commit comments

Comments
 (0)