Fix #16831: Fix console Table Widget does not render correctly in combination with ANSI formatting

This commit is contained in:
Ivan Sidorov
2020-10-23 22:21:03 +03:00
committed by GitHub
parent ed1c087784
commit cd36f505b1
5 changed files with 290 additions and 21 deletions

View File

@@ -13,6 +13,7 @@ Yii Framework 2 Change Log
- Bug #18308: Fixed `\yii\base\Model::getErrorSummary()` reverse order (DrDeath72)
- Bug #18313: Fix multipart form data parse with double quotes (wsaid)
- Bug #18317: Additional PHP 8 compatibility fixes (samdark, bizley)
- Bug #16831: Fix console Table Widget does not render correctly in combination with ANSI formatting (issidorov, cebe)
2.0.38 September 14, 2020
-------------------------

View File

@@ -243,36 +243,35 @@ class Table extends Widget
$buffer = '';
$arrayPointer = [];
$finalChunk = [];
$alreadyPrintedCells = [];
$renderedChunkTexts = [];
for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
$buffer .= $spanLeft . ' ';
foreach ($size as $index => $cellSize) {
$cell = isset($row[$index]) ? $row[$index] : null;
$prefix = '';
$chunk = '';
if ($index !== 0) {
$buffer .= $spanMiddle . ' ';
}
if (is_array($cell)) {
if (empty($finalChunk[$index])) {
$finalChunk[$index] = '';
if (empty($renderedChunkTexts[$index])) {
$renderedChunkTexts[$index] = '';
$start = 0;
$prefix = $this->listPrefix;
if (!isset($arrayPointer[$index])) {
$arrayPointer[$index] = 0;
}
} else {
$start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset);
}
$chunk = $cell[$arrayPointer[$index]];
$finalChunk[$index] .= $chunk;
if (isset($cell[$arrayPointer[$index] + 1]) && $finalChunk[$index] === $cell[$arrayPointer[$index]]) {
$chunk = Console::ansiColorizedSubstr($cell[$arrayPointer[$index]], $start, $cellSize - 4);
$renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk);
$fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]);
if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) {
$arrayPointer[$index]++;
$finalChunk[$index] = '';
$renderedChunkTexts[$index] = '';
}
} else {
if (!isset($alreadyPrintedCells[$index])) {
$chunk = $cell;
}
$alreadyPrintedCells[$index] = true;
$chunk = Console::ansiColorizedSubstr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2);
}
$chunk = $prefix . $chunk;
$repeat = $cellSize - Console::ansiStrwidth($chunk) - 1;
@@ -346,15 +345,23 @@ class Table extends Widget
$totalWidth += $columnWidth;
}
$relativeWidth = $screenWidth / $totalWidth;
if ($totalWidth > $screenWidth) {
$minWidth = 3;
$fixWidths = [];
$relativeWidth = $screenWidth / $totalWidth;
foreach ($this->columnWidths as $j => $width) {
$this->columnWidths[$j] = (int) ($width * $relativeWidth);
if ($j === count($this->columnWidths)) {
$this->columnWidths = $totalWidth;
$scaledWidth = (int) ($width * $relativeWidth);
if ($scaledWidth < $minWidth) {
$fixWidths[$j] = 3;
}
}
$totalFixWidth = array_sum($fixWidths);
$relativeWidth = ($screenWidth - $totalFixWidth) / ($totalWidth - $totalFixWidth);
foreach ($this->columnWidths as $j => $width) {
if (!array_key_exists($j, $fixWidths)) {
$this->columnWidths[$j] = (int) ($width * $relativeWidth);
}
$totalWidth -= $this->columnWidths[$j];
}
}
}

View File

@@ -331,7 +331,7 @@ class BaseConsole
*/
public static function stripAnsiFormat($string)
{
return preg_replace('/\033\[[\d;?]*\w/', '', $string);
return preg_replace(self::ansiCodesPattern(), '', $string);
}
/**
@@ -355,6 +355,67 @@ class BaseConsole
return mb_strwidth(static::stripAnsiFormat($string), Yii::$app->charset);
}
/**
* Returns the portion with ANSI color codes of string specified by the start and length parameters.
* If string has color codes, then will be return "TEXT_COLOR + TEXT_STRING + DEFAULT_COLOR",
* else will be simple "TEXT_STRING".
* @param string $string
* @param int $start
* @param int $length
* @return string
*/
public static function ansiColorizedSubstr($string, $start, $length)
{
if ($start < 0 || $length <= 0) {
return '';
}
$textItems = preg_split(self::ansiCodesPattern(), $string);
preg_match_all(self::ansiCodesPattern(), $string, $colors);
$colors = count($colors) ? $colors[0] : [];
array_unshift($colors, '');
$result = '';
$curPos = 0;
$inRange = false;
foreach ($textItems as $k => $textItem) {
$color = $colors[$k];
if ($curPos <= $start && $start < $curPos + Console::ansiStrwidth($textItem)) {
$text = mb_substr($textItem, $start - $curPos, null, Yii::$app->charset);
$inRange = true;
} else {
$text = $textItem;
}
if ($inRange) {
$result .= $color . $text;
$diff = $length - Console::ansiStrwidth($result);
if ($diff <= 0) {
if ($diff < 0) {
$result = mb_substr($result, 0, $diff, Yii::$app->charset);
}
$defaultColor = static::renderColoredString('%n');
if ($color && $color != $defaultColor) {
$result .= $defaultColor;
}
break;
}
}
$curPos += mb_strlen($textItem, Yii::$app->charset);
}
return $result;
}
private static function ansiCodesPattern()
{
return /** @lang PhpRegExp */ '/\033\[[\d;?]*\w/';
}
/**
* Converts an ANSI formatted string to HTML.
*