Skip to content

[Console] Terminal Color Mode refactoring and force Color Mode #47407

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 1 commit into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Symfony/Component/Console/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private function parseColor(string $color, bool $background = false): string
}

if ('#' === $color[0]) {
return ($background ? '4' : '3').Terminal::getTermColorSupport()->convertFromHexToAnsiColorCode($color);
return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
}

if (isset(self::COLORS[$color])) {
Expand Down
38 changes: 32 additions & 6 deletions src/Symfony/Component/Console/Terminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

class Terminal
{
public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4;

private static ?AnsiColorMode $colorMode = null;
private static ?int $width = null;
private static ?int $height = null;
private static ?bool $stty = null;
Expand All @@ -23,18 +26,27 @@ class Terminal
* About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
* For more information about true color support with terminals https://github.com/termstandard/colors/.
*/
public static function getTermColorSupport(): AnsiColorMode
public static function getColorMode(): AnsiColorMode
{
// Use Cache from previous run (or user forced mode)
if (null !== self::$colorMode) {
return self::$colorMode;
}

// Try with $COLORTERM first
if (\is_string($colorterm = getenv('COLORTERM'))) {
$colorterm = strtolower($colorterm);

if (str_contains($colorterm, 'truecolor')) {
return AnsiColorMode::Ansi24;
self::setColorMode(AnsiColorMode::Ansi24);

return self::$colorMode;
}

if (str_contains($colorterm, '256color')) {
return AnsiColorMode::Ansi8;
self::setColorMode(AnsiColorMode::Ansi8);

return self::$colorMode;
}
}

Expand All @@ -43,15 +55,29 @@ public static function getTermColorSupport(): AnsiColorMode
$term = strtolower($term);

if (str_contains($term, 'truecolor')) {
return AnsiColorMode::Ansi24;
self::setColorMode(AnsiColorMode::Ansi24);

return self::$colorMode;
}

if (str_contains($term, '256color')) {
return AnsiColorMode::Ansi8;
self::setColorMode(AnsiColorMode::Ansi8);

return self::$colorMode;
}
}

return AnsiColorMode::Ansi4;
self::setColorMode(self::DEFAULT_COLOR_MODE);

return self::$colorMode;
}

/**
* Force a terminal color mode rendering.
*/
public static function setColorMode(?AnsiColorMode $colorMode): void
{
self::$colorMode = $colorMode;
}

/**
Expand Down
25 changes: 8 additions & 17 deletions src/Symfony/Component/Console/Tests/ColorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Color;
use Symfony\Component\Console\Output\AnsiColorMode;
use Symfony\Component\Console\Terminal;

class ColorTest extends TestCase
{
Expand All @@ -33,8 +35,7 @@ public function testAnsi4Colors()

public function testTrueColors()
{
$colorterm = getenv('COLORTERM');
putenv('COLORTERM=truecolor');
Terminal::setColorMode(AnsiColorMode::Ansi24);

try {
$color = new Color('#fff', '#000');
Expand All @@ -43,17 +44,13 @@ public function testTrueColors()
$color = new Color('#ffffff', '#000000');
$this->assertSame("\033[38;2;255;255;255;48;2;0;0;0m \033[39;49m", $color->apply(' '));
} finally {
(false !== $colorterm) ? putenv('COLORTERM='.$colorterm) : putenv('COLORTERM');
Terminal::setColorMode(null);
}
}

public function testDegradedTrueColorsToAnsi4()
{
$colorterm = getenv('COLORTERM');
$term = getenv('TERM');

putenv('COLORTERM=');
putenv('TERM=');
Terminal::setColorMode(AnsiColorMode::Ansi4);

try {
$color = new Color('#f00', '#ff0');
Expand All @@ -62,18 +59,13 @@ public function testDegradedTrueColorsToAnsi4()
$color = new Color('#c0392b', '#f1c40f');
$this->assertSame("\033[31;43m \033[39;49m", $color->apply(' '));
} finally {
(false !== $colorterm) ? putenv('COLORTERM='.$colorterm) : putenv('COLORTERM');
(false !== $term) ? putenv('TERM='.$term) : putenv('TERM');
Terminal::setColorMode(null);
}
}

public function testDegradedTrueColorsToAnsi8()
{
$colorterm = getenv('COLORTERM');
$term = getenv('TERM');

putenv('COLORTERM=');
putenv('TERM=symfonyTest-256color');
Terminal::setColorMode(AnsiColorMode::Ansi8);

try {
$color = new Color('#f57255', '#8993c0');
Expand All @@ -82,8 +74,7 @@ public function testDegradedTrueColorsToAnsi8()
$color = new Color('#000000', '#ffffff');
$this->assertSame("\033[38;5;16;48;5;231m \033[39;49m", $color->apply(' '));
} finally {
(false !== $colorterm) ? putenv('COLORTERM='.$colorterm) : putenv('COLORTERM');
(false !== $term) ? putenv('TERM='.$term) : putenv('TERM');
Terminal::setColorMode(null);
}
}
}
29 changes: 26 additions & 3 deletions src/Symfony/Component/Console/Tests/TerminalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function testSttyOnWindows()
/**
* @dataProvider provideTerminalColorEnv
*/
public function testGetTermColorSupport(?string $testColorTerm, ?string $testTerm, AnsiColorMode $expected)
public function testGetColorMode(?string $testColorTerm, ?string $testTerm, AnsiColorMode $expected)
{
$oriColorTerm = getenv('COLORTERM');
$oriTerm = getenv('TERM');
Expand All @@ -107,10 +107,11 @@ public function testGetTermColorSupport(?string $testColorTerm, ?string $testTer
putenv($testColorTerm ? "COLORTERM={$testColorTerm}" : 'COLORTERM');
putenv($testTerm ? "TERM={$testTerm}" : 'TERM');

$this->assertSame($expected, Terminal::getTermColorSupport());
$this->assertSame($expected, Terminal::getColorMode());
} finally {
(false !== $oriColorTerm) ? putenv('COLORTERM='.$oriColorTerm) : putenv('COLORTERM');
(false !== $oriTerm) ? putenv('TERM='.$oriTerm) : putenv('TERM');
Terminal::setColorMode(null);
}
}

Expand All @@ -123,6 +124,28 @@ public function provideTerminalColorEnv(): \Generator
yield [null, 'xterm-TRUECOLOR', AnsiColorMode::Ansi24];
yield [null, 'xterm-256color', AnsiColorMode::Ansi8];
yield [null, 'xterm-256COLOR', AnsiColorMode::Ansi8];
yield [null, null, AnsiColorMode::Ansi4];
yield [null, null, Terminal::DEFAULT_COLOR_MODE];
}

public function testSetColorMode()
{
$oriColorTerm = getenv('COLORTERM');
$oriTerm = getenv('TERM');

try {
putenv('COLORTERM');
putenv('TERM');
$this->assertSame(Terminal::DEFAULT_COLOR_MODE, Terminal::getColorMode());

putenv('COLORTERM=256color');
$this->assertSame(Terminal::DEFAULT_COLOR_MODE, Terminal::getColorMode()); // Terminal color mode is cached at first call. Terminal cannot change during execution.

Terminal::setColorMode(AnsiColorMode::Ansi24); // Force change by user.
$this->assertSame(AnsiColorMode::Ansi24, Terminal::getColorMode());
} finally {
(false !== $oriColorTerm) ? putenv('COLORTERM='.$oriColorTerm) : putenv('COLORTERM');
(false !== $oriTerm) ? putenv('TERM='.$oriTerm) : putenv('TERM');
Terminal::setColorMode(null);
}
}
}