diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md
index 7bec19bcbdacf..8727c6d98abbe 100644
--- a/src/Symfony/Component/Console/CHANGELOG.md
+++ b/src/Symfony/Component/Console/CHANGELOG.md
@@ -5,6 +5,8 @@ CHANGELOG
-----
* Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester`
+ * added support for multiline responses to questions through `Question::setMultiline()`
+ and `Question::isMultiline()`
5.1.0
-----
diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index 18d148d778704..11d510ef43d7b 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -129,7 +129,7 @@ private function doAsk(OutputInterface $output, Question $question)
}
if (false === $ret) {
- $ret = fgets($inputStream, 4096);
+ $ret = $this->readInput($inputStream, $question);
if (false === $ret) {
throw new MissingInputException('Aborted.');
}
@@ -502,4 +502,26 @@ private function isInteractiveInput($inputStream): bool
return self::$stdinIsInteractive = 1 !== $status;
}
+
+ /**
+ * Reads one or more lines of input and returns what is read.
+ *
+ * @param resource $inputStream The handler resource
+ * @param Question $question The question being asked
+ *
+ * @return string|bool The input received, false in case input could not be read
+ */
+ private function readInput($inputStream, Question $question)
+ {
+ if (!$question->isMultiline()) {
+ return fgets($inputStream, 4096);
+ }
+
+ $ret = '';
+ while (false !== ($char = fgetc($inputStream))) {
+ $ret .= $char;
+ }
+
+ return $ret;
+ }
}
diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
index e4e87b2f99188..6c2596dfa7a2c 100644
--- a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
@@ -33,6 +33,10 @@ protected function writePrompt(OutputInterface $output, Question $question)
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
$default = $question->getDefault();
+ if ($question->isMultiline()) {
+ $text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
+ }
+
switch (true) {
case null === $default:
$text = sprintf(' %s:', $text);
@@ -93,4 +97,13 @@ protected function writeError(OutputInterface $output, \Exception $error)
parent::writeError($output, $error);
}
+
+ private function getEofShortcut(): string
+ {
+ if (false !== strpos(PHP_OS, 'WIN')) {
+ return 'Ctrl+Z then Enter';
+ }
+
+ return 'Ctrl+D';
+ }
}
diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php
index 8b0e4d989a900..e8d1342768720 100644
--- a/src/Symfony/Component/Console/Question/Question.php
+++ b/src/Symfony/Component/Console/Question/Question.php
@@ -30,6 +30,7 @@ class Question
private $default;
private $normalizer;
private $trimmable = true;
+ private $multiline = false;
/**
* @param string $question The question to ask to the user
@@ -61,6 +62,26 @@ public function getDefault()
return $this->default;
}
+ /**
+ * Returns whether the user response accepts newline characters.
+ */
+ public function isMultiline(): bool
+ {
+ return $this->multiline;
+ }
+
+ /**
+ * Sets whether the user response should accept newline characters.
+ *
+ * @return $this
+ */
+ public function setMultiline(bool $multiline): self
+ {
+ $this->multiline = $multiline;
+
+ return $this;
+ }
+
/**
* Returns whether the user response must be hidden.
*
diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
index 00113ef248920..0b6f8e324383c 100644
--- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
@@ -442,6 +442,40 @@ public function testAskHiddenResponseTrimmed()
$this->assertEquals(' 8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM')), $this->createOutputInterface(), $question));
}
+ public function testAskMultilineResponseWithEOF()
+ {
+ $essay = <<<'EOD'
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque pretium lectus quis suscipit porttitor. Sed pretium bibendum vestibulum.
+
+Etiam accumsan, justo vitae imperdiet aliquet, neque est sagittis mauris, sed interdum massa leo id leo.
+
+Aliquam rhoncus, libero ac blandit convallis, est sapien hendrerit nulla, vitae aliquet tellus orci a odio. Aliquam gravida ante sit amet massa lacinia, ut condimentum purus venenatis.
+
+Vivamus et erat dictum, euismod neque in, laoreet odio. Aenean vitae tellus at leo vestibulum auctor id eget urna.
+EOD;
+
+ $response = $this->getInputStream($essay);
+
+ $dialog = new QuestionHelper();
+
+ $question = new Question('Write an essay');
+ $question->setMultiline(true);
+
+ $this->assertEquals($essay, $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
+ }
+
+ public function testAskMultilineResponseWithSingleNewline()
+ {
+ $response = $this->getInputStream("\n");
+
+ $dialog = new QuestionHelper();
+
+ $question = new Question('Write an essay');
+ $question->setMultiline(true);
+
+ $this->assertEquals('', $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question));
+ }
+
/**
* @dataProvider getAskConfirmationData
*/
diff --git a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
index 467f38b6d45c8..4bf604904aae7 100644
--- a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
@@ -211,4 +211,22 @@ private function assertOutputContains($expected, StreamOutput $output, $normaliz
$this->assertStringContainsString($expected, $stream);
}
+
+ public function testAskMultilineQuestionIncludesHelpText()
+ {
+ $expected = 'Write an essay (press Ctrl+D to continue)';
+
+ if (false !== strpos(PHP_OS, 'WIN')) {
+ $expected = 'Write an essay (press Ctrl+Z then Enter to continue)';
+ }
+
+ $question = new Question('Write an essay');
+ $question->setMultiline(true);
+
+ $helper = new SymfonyQuestionHelper();
+ $input = $this->createStreamableInputInterfaceMock($this->getInputStream('\\'));
+ $helper->ask($input, $output = $this->createOutputInterface(), $question);
+
+ $this->assertOutputContains($expected, $output);
+ }
}
diff --git a/src/Symfony/Component/Console/Tests/Question/QuestionTest.php b/src/Symfony/Component/Console/Tests/Question/QuestionTest.php
index 357fe4d77eea1..55e9d58d4a2c7 100644
--- a/src/Symfony/Component/Console/Tests/Question/QuestionTest.php
+++ b/src/Symfony/Component/Console/Tests/Question/QuestionTest.php
@@ -297,4 +297,18 @@ public function testGetNormalizerDefault()
{
self::assertNull($this->question->getNormalizer());
}
+
+ /**
+ * @dataProvider providerTrueFalse
+ */
+ public function testSetMultiline(bool $multiline)
+ {
+ self::assertSame($this->question, $this->question->setMultiline($multiline));
+ self::assertSame($multiline, $this->question->isMultiline());
+ }
+
+ public function testIsMultilineDefault()
+ {
+ self::assertFalse($this->question->isMultiline());
+ }
}