Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Console] A better progress bar #10356

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 3, 2014
Prev Previous commit
Next Next commit
[Console] fixed progress bar when using ANSI colors and Emojis
  • Loading branch information
fabpot committed Mar 2, 2014
commit 8c0022b769babc99e5d7761cacf5416a7ffb6e17
15 changes: 15 additions & 0 deletions 15 src/Symfony/Component/Console/Helper/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
* Helper is the base class for all helper classes.
*
Expand Down Expand Up @@ -103,4 +105,17 @@ public static function formatMemory($memory)

return sprintf('%d B', $memory);
}

public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
// remove <...> formatting
$string = $formatter->format($string);
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);

return self::strlen($string);
}
}
39 changes: 25 additions & 14 deletions 39 src/Symfony/Component/Console/Helper/ProgressBar.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,15 @@ public function display()
throw new \LogicException('You must start the progress bar before calling display().');
}

// these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
$self = $this;
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
$output = $this->output;
$messages = $this->messages;
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) {
if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
$text = call_user_func($formatter, $self);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
$text = call_user_func($formatter, $self, $output);
} elseif (isset($messages[$matches[1]])) {
$text = $messages[$matches[1]];
} else {
return $matches[0];
}
Expand Down Expand Up @@ -424,23 +427,31 @@ public function clear()
*/
private function overwrite($message)
{
$length = Helper::strlen($message);
$lines = explode("\n", $message);

// append whitespace to match the last line's length
// FIXME: on each line!!!!!
// FIXME: max of each line for lastMessagesLength or an array?
if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
// append whitespace to match the line's length
if (null !== $this->lastMessagesLength) {
foreach ($lines as $i => $line) {
if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $line)) {
$lines[$i] = str_pad($line, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}
}
}

// move back to the beginning of the progress bar before redrawing it
$this->output->write("\x0D");
if ($this->formatLineCount) {
$this->output->write(sprintf("\033[%dA", $this->formatLineCount));
}
$this->output->write($message);
$this->output->write(implode("\n", $lines));

$this->lastMessagesLength = Helper::strlen($message);
$this->lastMessagesLength = 0;
foreach ($lines as $line) {
$len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line);
if ($len > $this->lastMessagesLength) {
$this->lastMessagesLength = $len;
}
}
}

private function determineBestFormat()
Expand All @@ -460,11 +471,11 @@ private function determineBestFormat()
private static function initPlaceholderFormatters()
{
return array(
'bar' => function (ProgressBar $bar) {
'bar' => function (ProgressBar $bar, OutputInterface $output) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getStep() % $bar->getBarWidth());
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlen($bar->getProgressCharacter());
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
}

Expand Down
16 changes: 2 additions & 14 deletions 16 src/Symfony/Component/Console/Helper/TableHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ private function renderCell(array $row, $column, $cellFormat)
$width += strlen($cell) - mb_strlen($cell, $encoding);
}

$width += $this->strlen($cell) - $this->computeLengthWithoutDecoration($cell);
$width += $this->strlen($cell) - self::strlenWithoutDecoration($this->output->getFormatter(), $cell);

$content = sprintf($this->cellRowContentFormat, $cell);

Expand Down Expand Up @@ -486,7 +486,7 @@ private function getColumnWidth($column)
*/
private function getCellWidth(array $row, $column)
{
return isset($row[$column]) ? $this->computeLengthWithoutDecoration($row[$column]) : 0;
return isset($row[$column]) ? self::strlenWithoutDecoration($this->output->getFormatter(), $row[$column]) : 0;
}

/**
Expand All @@ -498,18 +498,6 @@ private function cleanup()
$this->numberOfColumns = null;
}

private function computeLengthWithoutDecoration($string)
{
$formatter = $this->output->getFormatter();
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);

$string = $formatter->format($string);
$formatter->setDecorated($isDecorated);

return $this->strlen($string);
}

/**
* {@inheritDoc}
*/
Expand Down
63 changes: 50 additions & 13 deletions 63 src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Console\Tests\Helper;

use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Output\StreamOutput;

class ProgressBarTest extends \PHPUnit_Framework_TestCase
Expand Down Expand Up @@ -206,7 +207,7 @@ public function testClear()
$this->assertEquals(
$this->generateOutput(' 0/50 [>---------------------------] 0%').
$this->generateOutput(' 25/50 [==============>-------------] 50%').
$this->generateOutput(''),
$this->generateOutput(' '),
stream_get_contents($output->getStream())
);
}
Expand Down Expand Up @@ -332,9 +333,53 @@ public function testMultilineFormat()
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(">---------------------------\nfoobar").
$this->generateOutput("=========>------------------\nfoobar").
$this->generateOutput("\n").
$this->generateOutput("============================\nfoobar"),
$this->generateOutput("=========>------------------\nfoobar ").
$this->generateOutput(" \n ").
$this->generateOutput("============================\nfoobar "),
stream_get_contents($output->getStream())
);
}

public function testAnsiColorsAndEmojis()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 15);
ProgressBar::setPlaceholderFormatterDefinition('memory', function (ProgressBar $bar) {
static $i = 0;
$mem = 100000 * $i;
$colors = $i++ ? '41;37' : '44;37';

return "\033[".$colors."m ".Helper::formatMemory($mem)." \033[0m";
});
$bar->setFormat(" \033[44;37m %title:-37s% \033[0m\n %current%/%max% %bar% %percent:3s%%\n 🏁 %remaining:-10s% %memory:37s%");
$bar->setBarCharacter($done = "\033[32m●\033[0m");
$bar->setEmptyBarCharacter($empty = "\033[31m●\033[0m");
$bar->setProgressCharacter($progress = "\033[32m➤ \033[0m");

$bar->setMessage('Starting the demo... fingers crossed', 'title');
$bar->start();
$bar->setMessage('Looks good to me...', 'title');
$bar->advance(4);
$bar->setMessage('Thanks, bye', 'title');
$bar->finish();

rewind($output->getStream());

$this->assertEquals(
$this->generateOutput(
" \033[44;37m Starting the demo... fingers crossed \033[0m\n".
" 0/15 ".$progress.str_repeat($empty, 26)." 0%\n".
" 🏁 1 sec \033[44;37m 0 B \033[0m"
).
$this->generateOutput(
" \033[44;37m Looks good to me... \033[0m\n".
" 4/15 ".str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n".
" 🏁 1 sec \033[41;37m 97 kB \033[0m"
).
$this->generateOutput(
" \033[44;37m Thanks, bye \033[0m\n".
" 15/15 ".str_repeat($done, 28)." 100%\n".
" 🏁 1 sec \033[41;37m 195 kB \033[0m"
),
stream_get_contents($output->getStream())
);
}
Expand All @@ -346,16 +391,8 @@ protected function getOutputStream($decorated = true)

protected function generateOutput($expected)
{
$expectedout = $expected;

if (null !== $this->lastMessagesLength) {
$expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}

$this->lastMessagesLength = strlen($expectedout);

$count = substr_count($expected, "\n");

return "\x0D".($count ? sprintf("\033[%dA", $count) : '').$expectedout;
return "\x0D".($count ? sprintf("\033[%dA", $count) : '').$expected;
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.