Skip to content

[WIP] [Console] Support "named", "ansi", "hex", and "rgb" color support to console formatter #26576

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

Closed

Conversation

robfrawley
Copy link
Contributor

@robfrawley robfrawley commented Mar 17, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #19844
License MIT
Doc PR TBD

This pull request adds additional color support to the OutputFormatterStyle class (including support for the new color syntaxes in OutputFormatter). Basically, this adds 16,581,375 additional colors to the Symfony console component. That means it will be 16581375 times better! </s>

Disclaimer

This pull request is still very much a work in progress. Frequent, breaking changes will likely be pushed. But, since this idea is still fluid, I'm open to any and all ideas, comments, and thoughts. Feel free to let me know what you think (good or bad) and help shape this important change to color handling within the Symfony console component.

Current PR Description

The implementation is being rewritten to allow for a more extensible implementation. A new description will follow shortly. For now, you can get a general (but not specifically accurate) idea of the purpose of this PR below by reading the prior PR description.

Prior PR Description

Supported Colors

  • <color-name>
    Provides existing normal colors per 3/4 bit ANSI escape codes.
  • default-<color-name>
    Provides a "normal" namespace alias for existing colors per 4 bit ANSI escape codes.
  • bright-<color-name>
    Provides a "bright" namespace for new bright colors per 4 bit codes.
  • mode-<color-int>
    Provides a "mode" namespace for new colors per 8-bit color codes.
  • mode-<r>,<g>,<b>
    Provides a "mode" namespace for new colors per 24-bit RBG codes.

Examples

Example color names:

yellow
cyan
normal:blue
bright:red
mode:33
mode:210
mode:0,0,0
mode:255,255,0

Example format inputs and their respective console outputs:

- format: "<fg=green;>[text]</>"
  output: "\033[32m[text]\033[39m"

- format: "<fg=norm[text]l:green;>[text]</>"
  output: "\033[32m[text]\033[39m"

- format: "<fg=bright-green;>[text]</>"
  output: "\033[92m[text]\033[39m"

- format: "<fg=green;bg=blue;>[text]</>"
  output: "\033[32;44m[text]\033[39;49m"

- format: "<fg=green;bg=norm[text]l:blue;>[text]</>"
  output: "\033[32;44m[text]\033[39;49m"

- format: "<fg=green;bg=bright-blue;>[text]</>"
  output: "\033[32;104m[text]\033[39;49m"

- format: "<fg=green;options=bold>[text]</>"
  output: "\033[32;1m[text]\033[39;22m"

- format: "<fg=norm[text]l:green;options=bold>[text]</>"
  output: "\033[32;1m[text]\033[39;22m"

- format: "<fg=bright-green;options=bold>[text]</>"
  output: "\033[92;1m[text]\033[39;22m"

- format: "<fg=green;bg=blue;options=bold>[text]</>"
  output: "\033[32;44;1m[text]\033[39;49;22m"

- format: "<fg=green;bg=norm[text]l:blue;options=bold>[text]</>"
  output: "\033[32;44;1m[text]\033[39;49;22m"

- format: "<fg=green;bg=bright-blue;options=bold>[text]</>"
  output: "\033[32;104;1m[text]\033[39;49;22m"

- format: "<fg=bright-green;bg=bright-blue;options=bold>[text]</>"
  output: "\033[92;104;1m[text]\033[39;49;22m"

- format: "<fg=green;options=reverse;><tag>str</tag></>"
  output: "\033[32;7m<tag>str</tag>\033[39;27m"

- format: "<fg=green;options=bold,underscore>[text]</>"
  output: "\033[32;1;4m[text]\033[39;22;24m"

- format: "<fg=green;options=bold,underscore,reverse;>[text]</>"
  output: "\033[32;1;4;7m[text]\033[39;22;24;27m"

- format: "<fg=mode-160;>[text]</>"
  output: "\033[38;5;160m[text]\033[39m"

- format: "<fg=mode-160;bg=mode-253>[text]</>"
  output: "\033[38;5;160;48;5;253m[text]\033[39;49m"

- format: "<fg=mode-160;bg=mode-253;options=reverse>[text]</>"
  output: "\033[38;5;160;48;5;253;7m[text]\033[39;49;27m"

- format: "<fg=mode-0,255,0;>[text]</>"
  output: "\033[38;2;0,255,0m[text]\033[39m"

- format: "<fg=mode-0,150,0;bg=mode-255,255,250;options=bold;>[text]</>"
  output: "\033[38;2;0;150;0;48;2;255;255;250;1m[text]\033[39;49;22m"

@robfrawley robfrawley force-pushed the feature-enhance-console-colors branch 2 times, most recently from 0a5e358 to 94d991f Compare March 18, 2018 00:09
- enables namespaces colors, including:
  - normal: 4-bit normal colors
  - bright: 4-bit bright colors
  - mode  : format "[0-9]" for 8-bit color or format "[0-9]+,[0-9]+,[0-9]+" for 24-bit RGB color

- examples of supported foreground/background colors:
  - green
  - bright:blue
  - mode:178
  - mode:20,0,100

- examples of supported format markup and resulting output:
  - "<bg=bright-blue;>[str]</>" -> "\033[104m[str]\033[49m"1
  - "<fg=bright-green;options=bold>[str]</>" -> "\033[92;1m[str]\033[39;22m"
  - "<fg=mode-54;bg=mode-253>[str]</>" -> "\033[38;5;54;48;5;253m[str]\033[39;49m"
  - "<fg=mode-7;bg=bright:red;options=reverse>[str]</>" -> "\033[38;5;7;101m[str]\033[39;49m"
  - "<fg=mode-0,255,0;bg=bright-cyan>[str]</>" -> "[38;2;0;255;0;106m[test][39;49m"
@robfrawley robfrawley force-pushed the feature-enhance-console-colors branch from 94d991f to eb1a92c Compare March 18, 2018 00:15
@robfrawley
Copy link
Contributor Author

robfrawley commented Mar 18, 2018

I'm planning to put some substantial effort into re-writing (basically) this complete pull request. While I'm generally happy with how the new color functionality in this PR works from the user-perspective, I cannot say the I'm particularly stoked about the actual underlying implementation I've cobbled together.

My changes were built around the existing OutputFormatterStyle color handling code. The original color handling worked well with only 8 supported colors (the currently released console implementation only supportes half of the 16 available 4-bit ANSI color codes to begin with). Now that I intend to introduce support for full 4-bit, 8-bit, and 24-bit ANSI color codes (which, together, includes some 16,777,487 available colors) the assumptions, shortcuts, and reliance on the existing implementation that I followed while creating these changes have resulted in my current, "okay, it works, but it ain't winning no awards" implementation, as seen in eb1a92c.

So, I'm'a re-write it. :-) I plan to strip the OutputFormatterStyle class of all color logic and re-implement it with a purposeful implementation geared toward supporting 24-bit, 8-bit, and 4-bit ANSI color codes.

I believe it would be sensible to build this class's color handling with first-class 24-bit color support. I envision allowing the user to assign their expected ANSI color level (4, 8, or 24) for the formatted outputs. Internally all colors will be handled as 24-bit, but the user setting will define if and how those 24-bit representations are coerced to their nearest lower-bit value when rendering the formatted output. Moreover, obviously support for the prior user inputs, (basic color string names, as well as all other variants allowed for within the 4-bit and 8-bit levels), will continue to be supported, but again, regardless of what the user inputs, the colors will internally be handled as 24-bit once parsed and coerced (if required).

Before I go through the work to realize this plan, can any @symfony/deciders provide some general feedback as to what I'm describing? Assuming it can be implemented without BC breaks, is it something @symfony/deciders would want to merge? Do you guys have any alternative ideas you'd like to offer? Basically, I just want to make sure I'm not going to waste a ton of time on something that may not be merged. :-) I'd appreciate any input!

All the best!

@javiereguiluz
Copy link
Member

@robfrawley I like this proposal a lot. Thanks for proposing it! I've been looking into other implementation to see what they do:


JavaScript: https://github.com/chalk/chalk

chalk.keyword('red')
chalk.keyword('redBright')
chalk.rgb(123, 45, 67)
chalk.hex('#DEADED')
chalk.hsv(32, 100, 100)

Rust: https://github.com/ogham/rust-ansi-term

You can access the extended range of 256 colours by using the Fixed colour variant:

use ansi_term::Colour::Fixed;
Fixed(134).paint("A sort of light purple");

You can also access full 24-bit color by using the RGB colour variant:

use ansi_term::Colour::RGB;
RGB(70, 130, 180).paint("Steel blue");

Go: https://godoc.org/github.com/gizak/termui

const (
    ColorDefault Attribute = iota
    ColorBlack
    ColorRed
    ColorGreen
    ColorYellow
    ColorBlue
    ColorMagenta
    ColorCyan
    ColorWhite
)

// ...

func ColorRGB(r, g, b int) Attribute

Some questions and comments:

  1. Is the normal: needed or is it equivalent to not adding it? blue and normal:blue are the same color? If true, we could remove normal.
  2. What if we replace bright:red by brightRed or redBright as others do?
// Before
<fg=bright:green;options=bold> ... </>

// After
<fg=brightGreen;options=bold> ... </>
  1. Instead of mode:, what if we define ansi() and rgb() for extended colors?
// Before
<fg=mode:160;bg=mode:253;options=reverse> ... </>
<fg=mode:0,150,0;bg=mode:255,255,250;options=bold;> ... </>

// After
<fg=ansi(160);bg=ansi(253);options=reverse> ... </>
<fg=rgb(0,150,0);bg=rgb(255,255,250);options=bold;> ... </>

@robfrawley
Copy link
Contributor Author

robfrawley commented Mar 18, 2018

@javiereguiluz Here are my overly verbose answers to your comments/questions ;-)

  • Currently, blue and normal:blue provide the same result; I agree the normal:blue syntax has no valid use case. In fact, it could very well cause confusion for consumers between it and the default color type. The "normal" syntax will be removed.

  • Let's tackle your third list item before your second: the alternate syntax recommendations. Interestingly, I had actually previously considered similar variations during the early development work on these changes! :-)

    I had decided against it and used the (awkward) syntaxes seen here, as I was attempting to limit changes (the complex syntaxes required edits outside OutputFormatter). But, as I said in [WIP] [Console] Support "named", "ansi", "hex", and "rgb" color support to console formatter #26576 (comment), my goals have changed; my current motivation is to implement a solid, full-color-based formatter, regardless of the collateral changes required.

    In short, I am 100% on-board with using a better syntax and I think your examples (generally) make a ton of sense. It would really be great if we could support a wide swath of color inputs but we need to decide where to draw the line. Which of the following items are least useful in your mind?

    magenta
    bright(magenta)
    ansi(127)
    rgb(255,0,255)
    hex(F0F)
    hsl(300,100,50)
    hsv(300,100,100)
    cmyk(0,100,0,0)

    The above example color syntaxes would all compile to the same color code \033[38;2;255,0,255m when 24-bit output is enabled in the formatter (except for the "bright" variant).

  • While I really dislike my prior syntax for "bright" colors, I'm also not fond of your recommendation to use colorBright... For one, I cannot think of any other use of uppercase characters in our style formatting markup, and I hate inconsistency (I can be somewhat OCD about it, in fact). Aside from that, the behavior seems confusing to document and somewhat "magic". Instead, how do you feel about using the "method call" syntax for that too (see my example above)? That seems much more explicit to me.

@javiereguiluz
Copy link
Member

About 2) I wouldn't add support for hex(), hsv(), hsl(), etc. I'd only support keywords, ANSI colors and RGB colors. That's more than enough to do anything.

About the "bright" colors, I don't know what to say. I don't like redBright much ... but I'm not sure about bright(red) either. We should answer to this question: "Is bright red a color different from red ... or just a modification to red color?" If it's really a color, we could call it brightred or bright_red, etc.

@robfrawley
Copy link
Contributor Author

robfrawley commented Mar 19, 2018

Regarding the supported 24-bit color input types, I agree that hsv(), hsl(), and cmyk() provide little to no value, but I'm on the fence about hex(), a color format some people greatly prefer to rgb() due to their background with web design and or CSS/HTML in general. Not sure, though; let's see if anyone else thinks hex() is overkill. We can come back to that after I finish the new implementation and either include or exclude it. As for hsv(), hsl(), and cmyk(), they will be removed from the proposal.

As for the "bright" syntax question, yes, "bright" variants are really just colors and not modifications; I think you are correct then, to say using the "method" syntax for bright colors doesn't make sense. Your two examples are both good; I would add bright-red and red-bright to your examples.

Here are the currently proposed syntaxes, all of them (except the "bright" variant) define "magenta":

magenta
bright-magenta  # or magenta-bright, brightmagenta, magentabright, bright_magenta, and magenta_bright
ansi(127)
rgb(255,0,255)
hex(F0F)        # or hex(FF00FF)

@julienfalque
Copy link
Contributor

I love this proposal!

👍 for supporting hexadecimal syntax as well. As @robfrawley said, it is quite common, especially with a web design background.

@nicolas-grekas nicolas-grekas added this to the 4.1 milestone Mar 19, 2018
@nicolas-grekas nicolas-grekas modified the milestones: 4.1, next Apr 20, 2018
@robfrawley robfrawley changed the title [WIP] [Console] Add console output format color support for "normal", "bright", and "mode" color namespaces [WIP] [Console] Support named, ansi, hex, and rgb color support to console formatter Jul 16, 2018
@robfrawley robfrawley changed the title [WIP] [Console] Support named, ansi, hex, and rgb color support to console formatter [WIP] [Console] Support "named", "ansi", "hex", and "rgb" color support to console formatter Jul 16, 2018
@fabpot
Copy link
Member

fabpot commented Oct 14, 2018

@robfrawley Any news on this one?

@apfelbox
Copy link
Contributor

apfelbox commented Oct 14, 2018

About the color functions: I really like hsv(), because you can easily get an "even distribution" of your used colors by usage of the hue value. So, it has the use case of having a dynamic number of used colors.

But you can always preprocess the values and make the calculation for it before passing it to the component.
Just wanted to add the use case for consideration, whether such a convenience feature is wanted in core.

Can't come up for any use case of hsl() and especially not for cmyk().

The rest of the proposal is ace, though 👌

@nicolas-grekas
Copy link
Member

@robfrawley I tried to rebase this PR on master but I'm not allowed to push on your fork. Could you please have a look at https://github.com/nicolas-grekas/symfony/tree/feature-enhance-console-colors?

@fabpot
Copy link
Member

fabpot commented May 13, 2020

See #36802

@fabpot fabpot closed this May 13, 2020
@nicolas-grekas nicolas-grekas modified the milestones: next, 5.1 May 16, 2020
fabpot added a commit that referenced this pull request Jun 10, 2020
This PR was merged into the 5.2-dev branch.

Discussion
----------

[Console] Add support for true colors

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | Fix #26576, Fix #19844 <!-- prefix each issue number with "Fix #", if any -->
| License       | MIT
| Doc PR        | -

This PR adds support for true colors in the Console component.

Instead of adding many ways to add more colors than the current "default" ones, I've opted to only add true color support via Hex CSS colors. If you have something else (RGB, HSV, ...), you need to first convert it to a CSS color. I've also decided to not support the ANSI 256 colors as most terminals support true colors nowadays.

If true colors are not supported by the terminal, we fall back to the "nearest" default color.

`<fg=green;bg=blue>` is now equivalent to `<fg=#00ff00;bg=#00f>`.

The `Color` class is usable outside of the Console framework as well:

```php
$color = new Color('black', 'white');
echo $color->apply("foo");
echo "\n";

$color = new Color('red', 'yellow');
echo $color->apply("foo");
echo "\n";

$color = new Color('#000000', '#ffffff');
echo $color->apply("foo");
$color = new Color('#000', '#fff', ['underscore', 'reverse']);
echo $color->apply("bar");
echo "\n";
```

Rainbow time!

```php
function rainbowColor($i) {
    $h = (int) ($i / 43);
    $f = (int) ($i - 43 * $h);
    $t = (int) ($f * 255 / 43);
    $q = 255 - $t;

    if ($h == 0) {
        return new Color('', sprintf('#FF%02x00', $t));
    } elseif ($h == 1) {
        return new Color('', sprintf('#%02xFF00', $q));
    } elseif ($h == 2) {
        return new Color('', sprintf('#00FF%02x', $t));
    } elseif ($h == 3) {
        return new Color('', sprintf('#00%02xFF', $q));
    } elseif ($h == 4) {
        return new Color('', sprintf('#%02x00FF', $t));
    } elseif ($h == 5) {
        return new Color('', sprintf('#FF00%02x', $q));
    }
}

for ($i = 0; $i < 128; $i++) {
    echo rainbowColor($i)->apply(' ');
}
echo "\n";

for ($i = 255; $i >= 128; $i--) {
    echo rainbowColor($i)->apply(' ');
}
echo "\n";
```

![image](https://user-images.githubusercontent.com/47313/81796170-59af5e00-950d-11ea-8203-18c13ae8a07e.png)

Commits
-------

d066514 [Console] Add support for true colors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants