diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index c046f5ec9f..af4d0b9cca 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; /** * The QuestionHelper class provides helpers to interact with the user. @@ -157,7 +158,7 @@ class QuestionHelper extends Helper $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterValues(); - if (null === $autocomplete || !$this->hasSttyAvailable()) { + if (null === $autocomplete || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { @@ -409,7 +410,7 @@ class QuestionHelper extends Helper return $value; } - if ($this->hasSttyAvailable()) { + if (Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); @@ -495,20 +496,4 @@ class QuestionHelper extends Helper return self::$shell; } - - /** - * Returns whether Stty is available or not. - * - * @return bool - */ - private function hasSttyAvailable() - { - if (null !== self::$stty) { - return self::$stty; - } - - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = 0 === $exitcode; - } } diff --git a/src/Symfony/Component/Console/Terminal.php b/src/Symfony/Component/Console/Terminal.php index 56eb050964..53a0f7890b 100644 --- a/src/Symfony/Component/Console/Terminal.php +++ b/src/Symfony/Component/Console/Terminal.php @@ -15,6 +15,7 @@ class Terminal { private static $width; private static $height; + private static $stty; /** * Gets the terminal width. @@ -54,6 +55,22 @@ class Terminal return self::$height ?: 50; } + /** + * @internal + * + * @return bool + */ + public static function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } + private static function initDimensions() { if ('\\' === \DIRECTORY_SEPARATOR) { @@ -62,12 +79,21 @@ class Terminal // or [w, h] from "wxh" self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (self::hasSttyAvailable()) { + self::initDimensionsUsingStty(); } elseif (null !== $dimensions = self::getConsoleMode()) { // extract [w, h] from "wxh" self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } - } elseif ($sttyString = self::getSttyColumns()) { + } else { + self::initDimensionsUsingStty(); + } + } + + private static function initDimensionsUsingStty() + { + if ($sttyString = self::getSttyColumns()) { if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { // extract [w, h] from "rows h; columns w;" self::$width = (int) $matches[2]; diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index d4ac0f4693..fc2589097c 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; /** * @group tty @@ -167,7 +168,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest public function testAskWithAutocomplete() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -200,7 +201,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest public function testAskWithAutocompleteWithNonSequentialKeys() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -219,7 +220,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest public function testAskWithAutocompleteWithExactMatch() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -255,7 +256,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest */ public function testAskWithAutocompleteWithMultiByteCharacter($character) { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -279,7 +280,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest public function testAutocompleteWithTrailingBackslash() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -672,7 +673,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest */ public function testLegacyAskWithAutocomplete() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -709,7 +710,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest */ public function testLegacyAskWithAutocompleteWithNonSequentialKeys() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -977,7 +978,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest public function testTraversableAutocomplete() { - if (!$this->hasSttyAvailable()) { + if (!Terminal::hasSttyAvailable()) { $this->markTestSkipped('`stty` is required to test autocomplete functionality'); } @@ -1062,13 +1063,6 @@ class QuestionHelperTest extends AbstractQuestionHelperTest return $mock; } - - private function hasSttyAvailable() - { - exec('stty 2>&1', $output, $exitcode); - - return 0 === $exitcode; - } } class AutocompleteValues implements \IteratorAggregate diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php index 93b8c44a78..546d2214c4 100644 --- a/src/Symfony/Component/Console/Tests/TerminalTest.php +++ b/src/Symfony/Component/Console/Tests/TerminalTest.php @@ -18,17 +18,31 @@ class TerminalTest extends TestCase { private $colSize; private $lineSize; + private $ansiCon; protected function setUp() { $this->colSize = getenv('COLUMNS'); $this->lineSize = getenv('LINES'); + $this->ansiCon = getenv('ANSICON'); + $this->resetStatics(); } protected function tearDown() { putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize); + putenv($this->ansiCon ? 'ANSICON='.$this->ansiCon : 'ANSICON'); + $this->resetStatics(); + } + + private function resetStatics() + { + foreach (['height', 'width', 'stty'] as $name) { + $property = new \ReflectionProperty(Terminal::class, $name); + $property->setAccessible(true); + $property->setValue(null); + } } public function test() @@ -56,4 +70,28 @@ class TerminalTest extends TestCase $this->assertSame(0, $terminal->getWidth()); $this->assertSame(0, $terminal->getHeight()); } + + public function testSttyOnWindows() + { + if ('\\' !== \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Must be on windows'); + } + + $sttyString = exec('(stty -a | grep columns) 2>&1', $output, $exitcode); + if (0 !== $exitcode) { + $this->markTestSkipped('Must have stty support'); + } + + $matches = []; + if (0 === preg_match('/columns.(\d+)/i', $sttyString, $matches)) { + $this->fail('Could not determine existing stty columns'); + } + + putenv('COLUMNS'); + putenv('LINES'); + putenv('ANSICON'); + + $terminal = new Terminal(); + $this->assertSame((int) $matches[1], $terminal->getWidth()); + } }