Skip to content

Commit 3c455e0

Browse files
committed
util: create hex style cache and fast path
1 parent 27c7f4d commit 3c455e0

1 file changed

Lines changed: 46 additions & 21 deletions

File tree

lib/util.js

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ const kEscapeEnd = 'm';
114114
const kDimCode = 2;
115115
const kBoldCode = 1;
116116

117+
// Close sequence for 24-bit foreground colors (reset to default foreground)
118+
const kHexCloseSeq = kEscape + '39' + kEscapeEnd;
119+
117120
let styleCache;
121+
const hexStyleCache = { __proto__: null };
118122

119123
function getStyleCache() {
120124
if (styleCache === undefined) {
@@ -137,6 +141,25 @@ function getStyleCache() {
137141
return styleCache;
138142
}
139143

144+
/**
145+
* Returns the cached ANSI escape sequences for a hex color.
146+
* Computes and caches on first use to avoid repeated Buffer allocations.
147+
* @param {string} hex A valid hex color string (#RGB or #RRGGBB)
148+
* @returns {{openSeq: string, closeSeq: string}}
149+
*/
150+
function getHexStyle(hex) {
151+
const cached = hexStyleCache[hex];
152+
if (cached !== undefined) return cached;
153+
const { 0: r, 1: g, 2: b } = hexToRgb(hex);
154+
const style = {
155+
__proto__: null,
156+
openSeq: kEscape + rgbToAnsi24Bit(r, g, b) + kEscapeEnd,
157+
closeSeq: kHexCloseSeq,
158+
};
159+
hexStyleCache[hex] = style;
160+
return style;
161+
}
162+
140163
function replaceCloseCode(str, closeSeq, openSeq, keepClose) {
141164
const closeLen = closeSeq.length;
142165
let index = str.indexOf(closeSeq);
@@ -163,15 +186,6 @@ function replaceCloseCode(str, closeSeq, openSeq, keepClose) {
163186
// Matches #RGB or #RRGGBB
164187
const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
165188

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-
175189
/**
176190
* Parses a hex color string into RGB components.
177191
* Supports both 3-digit (#RGB) and 6-digit (#RRGGBB) formats.
@@ -225,6 +239,17 @@ function styleText(format, text, options) {
225239
const processed = replaceCloseCode(text, style.closeSeq, style.openSeq, style.keepClose);
226240
return style.openSeq + processed + style.closeSeq;
227241
}
242+
243+
if (format[0] === '#') {
244+
let hexStyle = hexStyleCache[format];
245+
if (hexStyle === undefined && RegExpPrototypeExec(hexColorRegExp, format) !== null) {
246+
hexStyle = getHexStyle(format);
247+
}
248+
if (hexStyle !== undefined) {
249+
const processed = replaceCloseCode(text, hexStyle.closeSeq, hexStyle.openSeq, false);
250+
return hexStyle.openSeq + processed + hexStyle.closeSeq;
251+
}
252+
}
228253
}
229254

230255
validateString(text, 'text');
@@ -255,24 +280,24 @@ function styleText(format, text, options) {
255280
for (const key of formatArray) {
256281
if (key === 'none') continue;
257282

258-
if (isValidHexColor(key)) {
283+
if (typeof key === 'string' && key[0] === '#') {
284+
let hexStyle = hexStyleCache[key];
285+
if (hexStyle === undefined) {
286+
if (RegExpPrototypeExec(hexColorRegExp, key) === null) {
287+
throw new ERR_INVALID_ARG_VALUE('format', key,
288+
'must be a valid hex color (#RGB or #RRGGBB)');
289+
}
290+
hexStyle = getHexStyle(key);
291+
}
259292
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);
293+
openCodes += hexStyle.openSeq;
294+
closeCodes = hexStyle.closeSeq + closeCodes;
295+
processedText = replaceCloseCode(processedText, hexStyle.closeSeq, hexStyle.openSeq, false);
266296
continue;
267297
}
268298

269299
const style = cache[key];
270300
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-
}
276301
validateOneOf(key, 'format', ObjectGetOwnPropertyNames(inspect.colors));
277302
}
278303
openCodes += style.openSeq;

0 commit comments

Comments
 (0)