Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b85c73f

Browse filesBrowse files
araujoguiaduh95
authored andcommitted
util: colorize text with hex colors
PR-URL: #61556 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Jordan Harband <ljharb@gmail.com> Reviewed-By: René <contact.9a5d6388@renegade334.me.uk> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Claudio Wunder <cwunder@gnome.org> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent 28346d9 commit b85c73f
Copy full SHA for b85c73f

4 files changed

+344-2Lines changed: 344 additions & 2 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎benchmark/util/style-text.js‎

Copy file name to clipboardExpand all lines: benchmark/util/style-text.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const assert = require('node:assert');
77

88
const bench = common.createBenchmark(main, {
99
messageType: ['string', 'number', 'boolean', 'invalid'],
10-
format: ['red', 'italic', 'invalid'],
10+
format: ['red', 'italic', 'invalid', '#ff0000'],
1111
validateStream: [1, 0],
1212
n: [1e3],
1313
});
Collapse file

‎doc/api/util.md‎

Copy file name to clipboardExpand all lines: doc/api/util.md
+29-1Lines changed: 29 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2531,6 +2531,9 @@ added:
25312531
- v21.7.0
25322532
- v20.12.0
25332533
changes:
2534+
- version: REPLACEME
2535+
pr-url: https://github.com/nodejs/node/pull/61556
2536+
description: Add support for hexadecimal colors.
25342537
- version:
25352538
- v24.2.0
25362539
- v22.17.0
@@ -2550,7 +2553,8 @@ changes:
25502553
-->
25512554
25522555
* `format` {string | Array} A text format or an Array
2553-
of text formats defined in `util.inspect.colors`.
2556+
of text formats defined in `util.inspect.colors`, or a hex color in `#RGB`
2557+
or `#RRGGBB` form.
25542558
* `text` {string} The text to to be formatted.
25552559
* `options` {Object}
25562560
* `validateStream` {boolean} When true, `stream` is checked to see if it can handle colors. **Default:** `true`.
@@ -2613,6 +2617,30 @@ console.log(
26132617
26142618
The special format value `none` applies no additional styling to the text.
26152619
2620+
In addition to predefined color names, `util.styleText()` supports hex color
2621+
strings using ANSI TrueColor (24-bit) escape sequences. Hex colors can be
2622+
specified in either 3-digit (`#RGB`) or 6-digit (`#RRGGBB`) format:
2623+
2624+
```mjs
2625+
import { styleText } from 'node:util';
2626+
2627+
// 6-digit hex color
2628+
console.log(styleText('#ff5733', 'Orange text'));
2629+
2630+
// 3-digit hex color (shorthand)
2631+
console.log(styleText('#f00', 'Red text'));
2632+
```
2633+
2634+
```cjs
2635+
const { styleText } = require('node:util');
2636+
2637+
// 6-digit hex color
2638+
console.log(styleText('#ff5733', 'Orange text'));
2639+
2640+
// 3-digit hex color (shorthand)
2641+
console.log(styleText('#f00', 'Red text'));
2642+
```
2643+
26162644
The full list of formats can be found in [modifiers][].
26172645
26182646
## Class: `util.TextDecoder`
Collapse file

‎lib/util.js‎

Copy file name to clipboardExpand all lines: lib/util.js
+66Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const {
3737
ObjectSetPrototypeOf,
3838
ObjectValues,
3939
ReflectApply,
40+
RegExpPrototypeExec,
41+
StringPrototypeSlice,
4042
StringPrototypeToWellFormed,
4143
} = primordials;
4244

@@ -46,10 +48,12 @@ const {
4648
codes: {
4749
ERR_FALSY_VALUE_REJECTION,
4850
ERR_INVALID_ARG_TYPE,
51+
ERR_INVALID_ARG_VALUE,
4952
ERR_OUT_OF_RANGE,
5053
},
5154
isErrorStackTraceLimitWritable,
5255
} = require('internal/errors');
56+
const { Buffer } = require('buffer');
5357
const {
5458
format,
5559
formatWithOptions,
@@ -156,6 +160,51 @@ function replaceCloseCode(str, closeSeq, openSeq, keepClose) {
156160
return result + str.slice(lastIndex);
157161
}
158162

163+
// Matches #RGB or #RRGGBB
164+
const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
165+
166+
/**
167+
* Validates whether a string is a valid hex color code.
168+
* @param {string} hex The hex string to validate (e.g., '#fff' or '#ffffff')
169+
* @returns {boolean} True if valid hex color, false otherwise
170+
*/
171+
function isValidHexColor(hex) {
172+
return typeof hex === 'string' && RegExpPrototypeExec(hexColorRegExp, hex) !== null;
173+
}
174+
175+
/**
176+
* Parses a hex color string into RGB components.
177+
* Supports both 3-digit (#RGB) and 6-digit (#RRGGBB) formats.
178+
* @param {string} hex A valid hex color string
179+
* @returns {Buffer} The RGB components
180+
*/
181+
function hexToRgb(hex) {
182+
// Normalize to 6 digits
183+
let hexStr;
184+
if (hex.length === 4) {
185+
// Expand #RGB to #RRGGBB
186+
hexStr = hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
187+
} else if (hex.length === 7) {
188+
hexStr = StringPrototypeSlice(hex, 1);
189+
} else {
190+
throw new ERR_OUT_OF_RANGE('hex', '#RGB or #RRGGBB', hex);
191+
}
192+
193+
// TODO(araujogui): use Uint8Array.fromHex
194+
return Buffer.from(hexStr, 'hex');
195+
}
196+
197+
/**
198+
* Generates the ANSI TrueColor (24-bit) escape sequence for a foreground color.
199+
* @param {number} r Red component (0-255)
200+
* @param {number} g Green component (0-255)
201+
* @param {number} b Blue component (0-255)
202+
* @returns {string} The ANSI escape sequence
203+
*/
204+
function rgbToAnsi24Bit(r, g, b) {
205+
return `38;2;${r};${g};${b}`;
206+
}
207+
159208
/**
160209
* @param {string | string[]} format
161210
* @param {string} text
@@ -205,8 +254,25 @@ function styleText(format, text, options) {
205254

206255
for (const key of formatArray) {
207256
if (key === 'none') continue;
257+
258+
if (isValidHexColor(key)) {
259+
if (skipColorize) continue;
260+
const { 0: r, 1: g, 2: b } = hexToRgb(key);
261+
const openSeq = kEscape + rgbToAnsi24Bit(r, g, b) + kEscapeEnd;
262+
const closeSeq = kEscape + '39' + kEscapeEnd;
263+
openCodes += openSeq;
264+
closeCodes = closeSeq + closeCodes;
265+
processedText = replaceCloseCode(processedText, closeSeq, openSeq, false);
266+
continue;
267+
}
268+
208269
const style = cache[key];
209270
if (style === undefined) {
271+
// Check if it looks like an invalid hex color (starts with #)
272+
if (typeof key === 'string' && key[0] === '#') {
273+
throw new ERR_INVALID_ARG_VALUE('format', key,
274+
'must be a valid hex color (#RGB or #RRGGBB)');
275+
}
210276
validateOneOf(key, 'format', ObjectGetOwnPropertyNames(inspect.colors));
211277
}
212278
openCodes += style.openSeq;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.