diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php
index 255f3031d957e..fd3bbd12d30be 100644
--- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php
+++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php
@@ -142,6 +142,7 @@ public function formatAndWrap(string $message, int $width)
$offset = 0;
$output = '';
$tagRegex = '[a-z][a-z0-9,_=;-]*+';
+ $currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
@@ -152,7 +153,7 @@ public function formatAndWrap(string $message, int $width)
}
// add the text up to the next tag
- $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width);
+ $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
// opening tag?
@@ -166,7 +167,7 @@ public function formatAndWrap(string $message, int $width)
// >
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
- $output .= $this->applyCurrentStyle($text, $output, $width);
+ $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
} else {
@@ -174,7 +175,7 @@ public function formatAndWrap(string $message, int $width)
}
}
- $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width);
+ $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, array("\0" => '\\', '\\<' => '<'));
@@ -231,24 +232,46 @@ private function createStyleFromString(string $string)
/**
* Applies current style from stack to text, if must be applied.
*/
- private function applyCurrentStyle(string $text, string $current, int $width): string
+ private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
{
if ('' === $text) {
return '';
}
- if ($width) {
- if ('' !== $current) {
- $text = ltrim($text);
- }
+ if (!$width) {
+ return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
+ }
+
+ if (!$currentLineLength && '' !== $current) {
+ $text = ltrim($text);
+ }
+
+ if ($currentLineLength) {
+ $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
+ $text = substr($text, $i);
+ } else {
+ $prefix = '';
+ }
+
+ preg_match('~(\\n)$~', $text, $matches);
+ $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
+ $text = rtrim($text, "\n").($matches[1] ?? '');
- $text = wordwrap($text, $width, "\n", true);
+ if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
+ $text = "\n".$text;
+ }
+
+ $lines = explode("\n", $text);
+ if ($width === $currentLineLength = \strlen(end($lines))) {
+ $currentLineLength = 0;
+ }
- if ('' !== $current && "\n" !== substr($current, -1)) {
- $text = "\n".$text;
+ if ($this->isDecorated()) {
+ foreach ($lines as $i => $line) {
+ $lines[$i] = $this->styleStack->getCurrent()->apply($line);
}
}
- return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
+ return implode("\n", $lines);
}
}
diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php
index a895953dfb121..efa53203d9afd 100644
--- a/src/Symfony/Component/Console/Helper/Table.php
+++ b/src/Symfony/Component/Console/Helper/Table.php
@@ -13,6 +13,7 @@
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
@@ -80,6 +81,7 @@ class Table
* @var array
*/
private $columnWidths = array();
+ private $columnMaxWidths = array();
private static $styles;
@@ -222,6 +224,25 @@ public function setColumnWidths(array $widths)
return $this;
}
+ /**
+ * Sets the maximum width of a column.
+ *
+ * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
+ * formatted strings are preserved.
+ *
+ * @return $this
+ */
+ public function setColumnMaxWidth(int $columnIndex, int $width): self
+ {
+ if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
+ throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter())));
+ }
+
+ $this->columnMaxWidths[$columnIndex] = $width;
+
+ return $this;
+ }
+
public function setHeaders(array $headers)
{
$headers = array_values($headers);
@@ -434,7 +455,6 @@ private function renderColumnSeparator($type = self::BORDER_OUTSIDE)
* Example:
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
- *
*/
private function renderRow(array $row, string $cellFormat)
{
@@ -498,12 +518,17 @@ private function calculateNumberOfColumns($rows)
private function buildTableRows($rows)
{
+ /** @var WrappableOutputFormatterInterface $formatter */
+ $formatter = $this->output->getFormatter();
$unmergedRows = array();
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
+ if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
+ $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column]);
+ }
if (!strstr($cell, "\n")) {
continue;
}
@@ -711,8 +736,9 @@ private function getCellWidth(array $row, int $column): int
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
+ $cellWidth = max($cellWidth, $columnWidth);
- return max($cellWidth, $columnWidth);
+ return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
}
/**
diff --git a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
index b9a8559766696..b51668cfa7e20 100644
--- a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
+++ b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
@@ -327,11 +327,19 @@ public function testFormatAndWrap()
{
$formatter = new OutputFormatter(true);
- $this->assertSame("pre\n\033[37;41mfoo\nbar\nbaz\033[39;49m\npos\nt", $formatter->formatAndWrap('pre