Skip to content

Commit afed2f8

Browse files
committed
feature symfony#19012 [Console] progress bar fix (TomasVotruba, fabpot)
This PR was merged into the 3.2-dev branch. Discussion ---------- [Console] progress bar fix | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#13019 | License | MIT | Doc PR | - This is symfony#16490 where I've simplified the code as much as possible and added a test for the bug we're trying to fix. The main change is the renaming of the `TerminalDimensionsProvider` to just `Terminal`. The new class can probably be useful to add more about the terminal. Commits ------- 2f81247 switched to use COLUMNS and LINES env vars to change terminal dimensions bf7a5c5 fixed logic a589635 deprecated some Console Application methods 8f206c8 fixed CS, simplified code b030c24 [Console] ProgressBar - adjust to the window width (static)
2 parents 40f4c57 + 2f81247 commit afed2f8

File tree

7 files changed

+288
-137
lines changed

7 files changed

+288
-137
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 21 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,19 @@ class Application
6565
private $definition;
6666
private $helperSet;
6767
private $dispatcher;
68-
private $terminalDimensions;
68+
private $terminal;
6969
private $defaultCommand;
7070
private $singleCommand;
7171

7272
/**
73-
* Constructor.
74-
*
7573
* @param string $name The name of the application
7674
* @param string $version The version of the application
7775
*/
7876
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
7977
{
8078
$this->name = $name;
8179
$this->version = $version;
80+
$this->terminal = new Terminal();
8281
$this->defaultCommand = 'list';
8382
$this->helperSet = $this->getDefaultHelperSet();
8483
$this->definition = $this->getDefaultInputDefinition();
@@ -625,7 +624,7 @@ public function renderException(\Exception $e, OutputInterface $output)
625624

626625
$len = $this->stringWidth($title);
627626

628-
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
627+
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
629628
// HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
630629
if (defined('HHVM_VERSION') && $width > 1 << 31) {
631630
$width = 1 << 31;
@@ -689,60 +688,42 @@ public function renderException(\Exception $e, OutputInterface $output)
689688
* Tries to figure out the terminal width in which this application runs.
690689
*
691690
* @return int|null
691+
*
692+
* @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
692693
*/
693694
protected function getTerminalWidth()
694695
{
695-
$dimensions = $this->getTerminalDimensions();
696+
@trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
696697

697-
return $dimensions[0];
698+
return $this->terminal->getWidth();
698699
}
699700

700701
/**
701702
* Tries to figure out the terminal height in which this application runs.
702703
*
703704
* @return int|null
705+
*
706+
* @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
704707
*/
705708
protected function getTerminalHeight()
706709
{
707-
$dimensions = $this->getTerminalDimensions();
710+
@trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
708711

709-
return $dimensions[1];
712+
return $this->terminal->getHeight();
710713
}
711714

712715
/**
713716
* Tries to figure out the terminal dimensions based on the current environment.
714717
*
715718
* @return array Array containing width and height
719+
*
720+
* @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
716721
*/
717722
public function getTerminalDimensions()
718723
{
719-
if ($this->terminalDimensions) {
720-
return $this->terminalDimensions;
721-
}
724+
@trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
722725

723-
if ('\\' === DIRECTORY_SEPARATOR) {
724-
// extract [w, H] from "wxh (WxH)"
725-
if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
726-
return array((int) $matches[1], (int) $matches[2]);
727-
}
728-
// extract [w, h] from "wxh"
729-
if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
730-
return array((int) $matches[1], (int) $matches[2]);
731-
}
732-
}
733-
734-
if ($sttyString = $this->getSttyColumns()) {
735-
// extract [w, h] from "rows h; columns w;"
736-
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
737-
return array((int) $matches[2], (int) $matches[1]);
738-
}
739-
// extract [w, h] from "; h rows; w columns"
740-
if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
741-
return array((int) $matches[2], (int) $matches[1]);
742-
}
743-
}
744-
745-
return array(null, null);
726+
return array($this->terminal->getWidth(), $this->terminal->getHeight());
746727
}
747728

748729
/**
@@ -754,10 +735,15 @@ public function getTerminalDimensions()
754735
* @param int $height The height
755736
*
756737
* @return Application The current application
738+
*
739+
* @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
757740
*/
758741
public function setTerminalDimensions($width, $height)
759742
{
760-
$this->terminalDimensions = array($width, $height);
743+
@trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
744+
745+
putenv('COLUMNS='.$width);
746+
putenv('LINES='.$height);
761747

762748
return $this;
763749
}
@@ -927,54 +913,6 @@ protected function getDefaultHelperSet()
927913
));
928914
}
929915

930-
/**
931-
* Runs and parses stty -a if it's available, suppressing any error output.
932-
*
933-
* @return string
934-
*/
935-
private function getSttyColumns()
936-
{
937-
if (!function_exists('proc_open')) {
938-
return;
939-
}
940-
941-
$descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
942-
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
943-
if (is_resource($process)) {
944-
$info = stream_get_contents($pipes[1]);
945-
fclose($pipes[1]);
946-
fclose($pipes[2]);
947-
proc_close($process);
948-
949-
return $info;
950-
}
951-
}
952-
953-
/**
954-
* Runs and parses mode CON if it's available, suppressing any error output.
955-
*
956-
* @return string <width>x<height> or null if it could not be parsed
957-
*/
958-
private function getConsoleMode()
959-
{
960-
if (!function_exists('proc_open')) {
961-
return;
962-
}
963-
964-
$descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
965-
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
966-
if (is_resource($process)) {
967-
$info = stream_get_contents($pipes[1]);
968-
fclose($pipes[1]);
969-
fclose($pipes[2]);
970-
proc_close($process);
971-
972-
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
973-
return $matches[2].'x'.$matches[1];
974-
}
975-
}
976-
}
977-
978916
/**
979917
* Returns abbreviated suggestions in string format.
980918
*

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

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1515
use Symfony\Component\Console\Output\OutputInterface;
1616
use Symfony\Component\Console\Exception\LogicException;
17+
use Symfony\Component\Console\Terminal;
1718

1819
/**
1920
* The ProgressBar provides helpers to display progress output.
@@ -44,13 +45,12 @@ class ProgressBar
4445
private $formatLineCount;
4546
private $messages = array();
4647
private $overwrite = true;
48+
private $terminal;
4749

4850
private static $formatters;
4951
private static $formats;
5052

5153
/**
52-
* Constructor.
53-
*
5454
* @param OutputInterface $output An OutputInterface instance
5555
* @param int $max Maximum steps (0 if unknown)
5656
*/
@@ -62,6 +62,7 @@ public function __construct(OutputInterface $output, $max = 0)
6262

6363
$this->output = $output;
6464
$this->setMaxSteps($max);
65+
$this->terminal = new Terminal();
6566

6667
if (!$this->output->isDecorated()) {
6768
// disable overwrite when output does not support ANSI codes.
@@ -217,7 +218,7 @@ public function getProgressPercent()
217218
*/
218219
public function setBarWidth($size)
219220
{
220-
$this->barWidth = (int) $size;
221+
$this->barWidth = max(1, (int) $size);
221222
}
222223

223224
/**
@@ -412,21 +413,7 @@ public function display()
412413
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
413414
}
414415

415-
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
416-
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
417-
$text = call_user_func($formatter, $this, $this->output);
418-
} elseif (isset($this->messages[$matches[1]])) {
419-
$text = $this->messages[$matches[1]];
420-
} else {
421-
return $matches[0];
422-
}
423-
424-
if (isset($matches[2])) {
425-
$text = sprintf('%'.$matches[2], $text);
426-
}
427-
428-
return $text;
429-
}, $this->format));
416+
$this->overwrite($this->buildLine());
430417
}
431418

432419
/**
@@ -592,4 +579,38 @@ private static function initFormats()
592579
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
593580
);
594581
}
582+
583+
/**
584+
* @return string
585+
*/
586+
private function buildLine()
587+
{
588+
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
589+
$callback = function ($matches) {
590+
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
591+
$text = call_user_func($formatter, $this, $this->output);
592+
} elseif (isset($this->messages[$matches[1]])) {
593+
$text = $this->messages[$matches[1]];
594+
} else {
595+
return $matches[0];
596+
}
597+
598+
if (isset($matches[2])) {
599+
$text = sprintf('%'.$matches[2], $text);
600+
}
601+
602+
return $text;
603+
};
604+
$line = preg_replace_callback($regex, $callback, $this->format);
605+
606+
$lineLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line);
607+
$terminalWidth = $this->terminal->getWidth();
608+
if ($lineLength <= $terminalWidth) {
609+
return $line;
610+
}
611+
612+
$this->setBarWidth($this->barWidth - $lineLength + $terminalWidth);
613+
614+
return preg_replace_callback($regex, $callback, $this->format);
615+
}
595616
}

src/Symfony/Component/Console/Style/SymfonyStyle.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Style;
1313

14-
use Symfony\Component\Console\Application;
1514
use Symfony\Component\Console\Exception\RuntimeException;
1615
use Symfony\Component\Console\Formatter\OutputFormatter;
1716
use Symfony\Component\Console\Helper\Helper;
@@ -25,6 +24,7 @@
2524
use Symfony\Component\Console\Question\ChoiceQuestion;
2625
use Symfony\Component\Console\Question\ConfirmationQuestion;
2726
use Symfony\Component\Console\Question\Question;
27+
use Symfony\Component\Console\Terminal;
2828

2929
/**
3030
* Output decorator helpers for the Symfony Style Guide.
@@ -50,7 +50,8 @@ public function __construct(InputInterface $input, OutputInterface $output)
5050
$this->input = $input;
5151
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
5252
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
53-
$this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
53+
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
54+
$this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
5455

5556
parent::__construct($output);
5657
}
@@ -401,14 +402,6 @@ private function getProgressBar()
401402
return $this->progressBar;
402403
}
403404

404-
private function getTerminalWidth()
405-
{
406-
$application = new Application();
407-
$dimensions = $application->getTerminalDimensions();
408-
409-
return $dimensions[0] ?: self::MAX_LINE_LENGTH;
410-
}
411-
412405
private function autoPrependBlock()
413406
{
414407
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

0 commit comments

Comments
 (0)