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
Add RGB (256/Truecolor) support #140
Changes from all commits
7d69b22
8989561
d9cd275
b97e2d7
833cf89
91d7d5f
7771cc6
00caeaa
e23e9e8
5c9f1e1
adca689
d9bd5fb
4fe7e83
db10227
4b29e3f
8c873a3
09eaccb
2258fcc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,14 @@ var supportsColor = require('supports-color'); | |
var defineProps = Object.defineProperties; | ||
var isSimpleWindowsTerm = process.platform === 'win32' && !/^xterm/i.test(process.env.TERM); | ||
|
||
// supportsColor.level -> ansiStyles.color[name] mapping | ||
var levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; | ||
// color-convert models to exclude from the Chalk API due to conflicts and such. | ||
var skipModels = ['gray']; | ||
|
||
function Chalk(options) { | ||
// detect mode if not set manually | ||
this.enabled = !options || options.enabled === undefined ? supportsColor : options.enabled; | ||
// detect level if not set manually | ||
this.level = !options || options.level === undefined ? supportsColor.level : options.level; | ||
} | ||
|
||
// use bright blue on Windows as the normal blue color is illegible | ||
|
@@ -23,15 +28,53 @@ Object.keys(ansiStyles).forEach(function (key) { | |
|
||
styles[key] = { | ||
get: function () { | ||
return build.call(this, this._styles ? this._styles.concat(key) : [key]); | ||
var codes = ansiStyles[key]; | ||
return build.call(this, this._styles ? this._styles.concat(codes) : [codes], key); | ||
} | ||
}; | ||
}); | ||
|
||
ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not that? Chalk requires a closing code regex for building the style. No sense in re-calculating it for every single color-convert model in the loop right after it since it's the same for all of them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, it's a little bit dangerous to assign to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Chalk did it before in the loop above. We can change that, though - I'm not opposed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, it's not applied globally. We fixed that a while back :) |
||
Object.keys(ansiStyles.color.ansi).forEach(function (model) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a for-of loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we change the original loop as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but can do unrelated changes later so not to make this PR too noisy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should do a complete es2015-ification PR as a separate step to prevent history fragmentation, IMO. But I agree that it should happen. |
||
if (skipModels.indexOf(model) !== -1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tend to err on
It's obviously bike shedding. It'd be nice if XO made a definitive decision on it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True that. |
||
return; | ||
} | ||
|
||
styles[model] = { | ||
get: function () { | ||
var level = this.level; | ||
return function () { | ||
var open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); | ||
var codes = {open: open, close: ansiStyles.color.close, closeRe: ansiStyles.color.closeRe}; | ||
return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); | ||
}; | ||
} | ||
}; | ||
}); | ||
|
||
ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); | ||
Object.keys(ansiStyles.bgColor.ansi).forEach(function (model) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a for-of loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #140 (comment). |
||
if (skipModels.indexOf(model) !== -1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return; | ||
} | ||
|
||
var bgModel = 'bg' + model.charAt(0).toUpperCase() + model.substring(1); | ||
styles[bgModel] = { | ||
get: function () { | ||
var level = this.level; | ||
return function () { | ||
var open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); | ||
var codes = {open: open, close: ansiStyles.bgColor.close, closeRe: ansiStyles.bgColor.closeRe}; | ||
return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); | ||
}; | ||
} | ||
}; | ||
}); | ||
|
||
// eslint-disable-next-line func-names | ||
var proto = defineProps(function chalk() {}, styles); | ||
|
||
function build(_styles) { | ||
function build(_styles, key) { | ||
var builder = function () { | ||
return applyStyle.apply(builder, arguments); | ||
}; | ||
|
@@ -40,16 +83,19 @@ function build(_styles) { | |
|
||
builder._styles = _styles; | ||
|
||
Object.defineProperty(builder, 'enabled', { | ||
Object.defineProperty(builder, 'level', { | ||
enumerable: true, | ||
get: function () { | ||
return self.enabled; | ||
return self.level; | ||
}, | ||
set: function (v) { | ||
self.enabled = v; | ||
set: function (level) { | ||
self.level = level; | ||
} | ||
}); | ||
|
||
// see below for fix regarding invisible grey/dim combination on windows. | ||
builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; | ||
|
||
// __proto__ is used because we must return a function, but there is | ||
// no way to create a function with a different prototype. | ||
/* eslint-disable no-proto */ | ||
|
@@ -71,7 +117,7 @@ function applyStyle() { | |
} | ||
} | ||
|
||
if (!this.enabled || !str) { | ||
if (!this.level || !str) { | ||
return str; | ||
} | ||
|
||
|
@@ -82,12 +128,12 @@ function applyStyle() { | |
// see https://github.com/chalk/chalk/issues/58 | ||
// If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. | ||
var originalDim = ansiStyles.dim.open; | ||
if (isSimpleWindowsTerm && (nestedStyles.indexOf('gray') !== -1 || nestedStyles.indexOf('grey') !== -1)) { | ||
if (isSimpleWindowsTerm && this.hasGrey) { | ||
ansiStyles.dim.open = ''; | ||
} | ||
|
||
while (i--) { | ||
var code = ansiStyles[nestedStyles[i]]; | ||
var code = nestedStyles[i]; | ||
|
||
// Replace any instances already present with a re-opening code | ||
// otherwise only the part of the string until said closing code | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,14 +76,23 @@ CPU: ${chalk.red('90%')} | |
RAM: ${chalk.green('40%')} | ||
DISK: ${chalk.yellow('70%')} | ||
`); | ||
|
||
// Use RGB colors in terminal emulators that support it. | ||
log(chalk.keyword('orange')('Yay for orange colored text!')); | ||
log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); | ||
log(chalk.hex('#DEADED').bold('Bold gray!')); | ||
``` | ||
|
||
Easily define your own themes. | ||
|
||
```js | ||
const chalk = require('chalk'); | ||
|
||
const error = chalk.bold.red; | ||
const warning = chalk.keyword('orange'); | ||
|
||
console.log(error('Error!')); | ||
console.log(warning('Warning!')); | ||
``` | ||
|
||
Take advantage of console.log [string substitution](http://nodejs.org/docs/latest/api/console.html#console_console_log_data). | ||
|
@@ -105,16 +114,23 @@ Chain [styles](#styles) and call the last one as a method with a string argument | |
|
||
Multiple arguments will be separated by space. | ||
|
||
### chalk.enabled | ||
### chalk.level | ||
|
||
Color support is automatically detected, but you can override it by setting the `enabled` property. You should however only do this in your own code as it applies globally to all chalk consumers. | ||
Color support is automatically detected, but you can override it by setting the `level` property. You should however only do this in your own code as it applies globally to all chalk consumers. | ||
|
||
If you need to change this in a reusable module create a new instance: | ||
|
||
```js | ||
const ctx = new chalk.constructor({enabled: false}); | ||
const ctx = new chalk.constructor({level: 0}); | ||
``` | ||
|
||
Levels are as follows: | ||
|
||
0. All colors disabled | ||
1. Basic color support (16 colors) | ||
2. 256 color support | ||
3. RGB/Truecolor support (16 million colors) | ||
|
||
### chalk.supportsColor | ||
|
||
Detect whether the terminal [supports color](https://github.com/chalk/supports-color). Used internally and handled for you, but exposed for convenience. | ||
|
@@ -174,10 +190,42 @@ console.log(chalk.styles.red.open + 'Hello' + chalk.styles.red.close); | |
- `bgWhite` | ||
|
||
|
||
## 256-colors | ||
## 256/16 million (Truecolor) color support | ||
|
||
Chalk supports 256 colors and, when manually specified, [Truecolor (16 million colors)](https://gist.github.com/XVilka/8346728) on all supported terminal emulators. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for comma after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not? It's a qualifier for the subsequent clause. Grammatically the lack of a comma wouldn't make sense here. |
||
|
||
Colors are downsampled from 16 million RGB values to an ANSI color format that is supported by the terminal emulator (or by specifying {level: n} as a chalk option). For example, Chalk configured to run at level 1 (basic color support) will downsample an RGB value of #FF0000 (red) to 31 (ANSI escape for red). | ||
|
||
Some examples: | ||
|
||
- `chalk.hex('#DEADED').underline('Hello, world!')` | ||
- `chalk.keyword('orange')('Some orange text')` | ||
- `chalk.rgb(15, 100, 204).inverse('Hello!')` | ||
|
||
Background versions of these models are prefixed with `bg` and the first level of the module capitalized (e.g. `keyword` for foreground colors and `bgKeyword` for background colors). | ||
|
||
- `chalk.bgHex('#DEADED').underline('Hello, world!')` | ||
- `chalk.bgKeyword('orange')('Some orange text')` | ||
- `chalk.bgRgb(15, 100, 204).inverse('Hello!')` | ||
|
||
As of this writing, these are the supported color models that are exposed in Chalk: | ||
|
||
Chalk does not support anything other than the base eight colors, which guarantees it will work on all terminals and systems. Some terminals, specifically `xterm` compliant ones, will support the full range of 8-bit colors. For this the lower level [ansi-256-colors](https://github.com/jbnicolai/ansi-256-colors) package can be used. | ||
- `rgb` - e.g. `chalk.rgb(255, 136, 0).bold('Orange!')` | ||
- `hex` - e.g. `chalk.hex('#ff8800').bold('Orange!')` | ||
- `keyword` (CSS keywords) - e.g. `chalk.keyword('orange').bold('Orange!')` | ||
- `hsl` - e.g. `chalk.hsl(32, 100, 50).bold('Orange!')` | ||
- `hsv` | ||
- `hwb` | ||
- `cmyk` | ||
- `xyz` | ||
- `lab` | ||
- `lch` | ||
- `ansi16` | ||
- `ansi256` | ||
- `hcg` | ||
- `apple` (see [qix-/color-convert#30](https://github.com/Qix-/color-convert/issues/30)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think one example per color model would be very helpful. Show how a single color is represented through all the various methods. Maybe through a table that contains There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of these color models are not very practically useful though. I doubt most of these will be used by users. I would prefer to only document the most popular ones and rather leave it to color-convert to document the rest. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other thing is that the way color-convert is versioned all new models that are added 'just work' with whatever is using them assuming you're using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with just documenting the most popular ones. I think these are rgb,hex,keyword,hsl,cymk. We can link to the color-convert docs for further reading. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @silverwind 👍 Except for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So what should be put there, then? Just a basic description? Or a line of sample code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expand the examples to include above 4 using the same color. After that, I guess some work is needed on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I need to start enforcing readme updates in Can do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How's that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Examples looking good. I guess it's cleaner to remove the models without examples once color-convert's has examples for those. |
||
|
||
For a complete list of color models, see [`color-convert`'s list of conversions](https://github.com/Qix-/color-convert/blob/master/conversions.js). | ||
|
||
## Windows | ||
|
||
|
@@ -194,6 +242,7 @@ If you're on Windows, do yourself a favor and use [`cmder`](http://cmder.net/) i | |
- [ansi-regex](https://github.com/chalk/ansi-regex) - Regular expression for matching ANSI escape codes | ||
- [wrap-ansi](https://github.com/chalk/wrap-ansi) - Wordwrap a string with ANSI escape codes | ||
- [slice-ansi](https://github.com/chalk/slice-ansi) - Slice a string with ANSI escape codes | ||
- [color-convert](https://github.com/qix-/color-convert) - Converts colors between different models | ||
|
||
|
||
## License | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should still support
enabled
. Not enough benefit to make such a big breaking change. We can support both. Most will only need to know whether it's enabled or not anyways.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we handle when someone supplies
{level: 0, enabled: true}
or{level: 2, enabled: false}
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How you handle mutually exclusive things in general, you throw a user-friendly error, and document it well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started making this change and it just doesn't make sense. The code is super hairy - having to check to see if keys are in the configuration object and then do some arbitrary logic on the values. What does
enabled: true
mean now? A straight-up level of1
or do we usesupportsColor.level
?We're already bumping the major version and those advanced users using forced enable/disable outside of the functionality supplied by
supports-color
will be willing to adjust how they want Chalk to behave.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote on leaving
enabled
as well, mostly for backwards compatibility reasons.This seems like a good solution. Anything potentially bad with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code you posted is fine, but I think
enabled
needs to be defined viaObject.defineProperty
and modify/readthis.level
under the hood to support that:Please correct me if that's wrong, I may be missing things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing like you do, but I doubt many people use that. To say the truth, I discovered it just now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally think the code I posted is bloated and huge just to support a property that doesn't really make sense now that there are different levels of support instead of a boolean on/off value.
If we were to do this, then I agree;
.enabled
would have to reflect the.level
given a getter/setter descriptor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm only voting for
enabled
prop, because I see it somewhat often used in tests to conveniently enable/disable colors when testing output.Let's see what other guys think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The level prop will have the same functionality, though :) haha