-
-
Notifications
You must be signed in to change notification settings - Fork 35.5k
util: create hex style cache and fast path #62999
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
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 |
|---|---|---|
|
|
@@ -114,7 +114,11 @@ const kEscapeEnd = 'm'; | |
| const kDimCode = 2; | ||
| const kBoldCode = 1; | ||
|
|
||
| // Close sequence for 24-bit foreground colors (reset to default foreground) | ||
| const kHexCloseSeq = kEscape + '39' + kEscapeEnd; | ||
|
|
||
| let styleCache; | ||
| const hexStyleCache = { __proto__: null }; | ||
|
|
||
|
Comment on lines
+121
to
122
Member
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. isn't the maximum possible permutations here 16,781,312 - is the memory size of 17 million key/value pairs a massive concern?
Member
Author
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. Great point, altought a LRU doesn't seem a bad idea |
||
| function getStyleCache() { | ||
| if (styleCache === undefined) { | ||
|
|
@@ -137,6 +141,25 @@ function getStyleCache() { | |
| return styleCache; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the cached ANSI escape sequences for a hex color. | ||
| * Computes and caches on first use to avoid repeated Buffer allocations. | ||
| * @param {string} hex A valid hex color string (#RGB or #RRGGBB) | ||
| * @returns {{openSeq: string, closeSeq: string}} | ||
| */ | ||
| function getHexStyle(hex) { | ||
| const cached = hexStyleCache[hex]; | ||
| if (cached !== undefined) return cached; | ||
| const { 0: r, 1: g, 2: b } = hexToRgb(hex); | ||
| const style = { | ||
| __proto__: null, | ||
| openSeq: kEscape + rgbToAnsi24Bit(r, g, b) + kEscapeEnd, | ||
| closeSeq: kHexCloseSeq, | ||
| }; | ||
| hexStyleCache[hex] = style; | ||
| return style; | ||
| } | ||
|
|
||
| function replaceCloseCode(str, closeSeq, openSeq, keepClose) { | ||
| const closeLen = closeSeq.length; | ||
| let index = str.indexOf(closeSeq); | ||
|
|
@@ -163,15 +186,6 @@ function replaceCloseCode(str, closeSeq, openSeq, keepClose) { | |
| // Matches #RGB or #RRGGBB | ||
| const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; | ||
|
|
||
| /** | ||
| * Validates whether a string is a valid hex color code. | ||
| * @param {string} hex The hex string to validate (e.g., '#fff' or '#ffffff') | ||
| * @returns {boolean} True if valid hex color, false otherwise | ||
| */ | ||
| function isValidHexColor(hex) { | ||
| return typeof hex === 'string' && RegExpPrototypeExec(hexColorRegExp, hex) !== null; | ||
| } | ||
|
|
||
| /** | ||
| * Parses a hex color string into RGB components. | ||
| * Supports both 3-digit (#RGB) and 6-digit (#RRGGBB) formats. | ||
|
|
@@ -225,6 +239,17 @@ function styleText(format, text, options) { | |
| const processed = replaceCloseCode(text, style.closeSeq, style.openSeq, style.keepClose); | ||
| return style.openSeq + processed + style.closeSeq; | ||
| } | ||
|
|
||
| if (format[0] === '#') { | ||
| let hexStyle = hexStyleCache[format]; | ||
| if (hexStyle === undefined && RegExpPrototypeExec(hexColorRegExp, format) !== null) { | ||
| hexStyle = getHexStyle(format); | ||
| } | ||
| if (hexStyle !== undefined) { | ||
| const processed = replaceCloseCode(text, hexStyle.closeSeq, hexStyle.openSeq, false); | ||
| return hexStyle.openSeq + processed + hexStyle.closeSeq; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| validateString(text, 'text'); | ||
|
|
@@ -255,24 +280,24 @@ function styleText(format, text, options) { | |
| for (const key of formatArray) { | ||
| if (key === 'none') continue; | ||
|
|
||
| if (isValidHexColor(key)) { | ||
| if (typeof key === 'string' && key[0] === '#') { | ||
| let hexStyle = hexStyleCache[key]; | ||
| if (hexStyle === undefined) { | ||
| if (RegExpPrototypeExec(hexColorRegExp, key) === null) { | ||
| throw new ERR_INVALID_ARG_VALUE('format', key, | ||
| 'must be a valid hex color (#RGB or #RRGGBB)'); | ||
| } | ||
| hexStyle = getHexStyle(key); | ||
| } | ||
| if (skipColorize) continue; | ||
|
araujogui marked this conversation as resolved.
Outdated
|
||
| const { 0: r, 1: g, 2: b } = hexToRgb(key); | ||
| const openSeq = kEscape + rgbToAnsi24Bit(r, g, b) + kEscapeEnd; | ||
| const closeSeq = kEscape + '39' + kEscapeEnd; | ||
| openCodes += openSeq; | ||
| closeCodes = closeSeq + closeCodes; | ||
| processedText = replaceCloseCode(processedText, closeSeq, openSeq, false); | ||
| openCodes += hexStyle.openSeq; | ||
| closeCodes = hexStyle.closeSeq + closeCodes; | ||
| processedText = replaceCloseCode(processedText, hexStyle.closeSeq, hexStyle.openSeq, false); | ||
| continue; | ||
| } | ||
|
|
||
| const style = cache[key]; | ||
| if (style === undefined) { | ||
| // Check if it looks like an invalid hex color (starts with #) | ||
| if (typeof key === 'string' && key[0] === '#') { | ||
| throw new ERR_INVALID_ARG_VALUE('format', key, | ||
| 'must be a valid hex color (#RGB or #RRGGBB)'); | ||
| } | ||
| validateOneOf(key, 'format', ObjectGetOwnPropertyNames(inspect.colors)); | ||
| } | ||
| openCodes += style.openSeq; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 note the benchmarks are all repeating the same lookup – I imagine that using a plain object as the map here could end up deoptimising if the cache miss rate is high, eg. colours being randomised by the user? Appreciate this is probably quite niche in reality, but might be worth a benchmark case to see how something like a SafeMap would compare.