Skip to content

Commit 90032cf

Browse files
committed
util: respect nested formats in styleText
1 parent 229cc3b commit 90032cf

File tree

2 files changed

+48
-6
lines changed

2 files changed

+48
-6
lines changed

lib/util.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ function escapeStyleCode(code) {
108108
return `\u001b[${code}m`;
109109
}
110110

111+
// eslint-disable-next-line no-control-regex
112+
const endingRegex = /\u001b\[[34]9m/g;
113+
111114
/**
112115
* @param {string | string[]} format
113116
* @param {string} text
@@ -134,24 +137,58 @@ function styleText(format, text, { validateStream = true, stream = process.stdou
134137
skipColorize = !lazyUtilColors().shouldColorize(stream);
135138
}
136139

140+
if (skipColorize) {
141+
return text;
142+
}
143+
137144
// If the format is not an array, convert it to an array
138145
const formatArray = ArrayIsArray(format) ? format : [format];
139146

140-
let left = '';
141-
let right = '';
147+
// Collect the codes we're applying
148+
const codes = [];
142149
for (const key of formatArray) {
143150
if (key === 'none') continue;
144151
const formatCodes = inspect.colors[key];
145152
// If the format is not a valid style, throw an error
146153
if (formatCodes == null) {
147154
validateOneOf(key, 'format', ObjectKeys(inspect.colors));
148155
}
149-
if (skipColorize) continue;
150-
left += escapeStyleCode(formatCodes[0]);
151-
right = `${escapeStyleCode(formatCodes[1])}${right}`;
156+
157+
codes.push(formatCodes);
158+
}
159+
160+
if (codes.length === 0) {
161+
return text;
162+
}
163+
164+
// Build opening codes
165+
let openCodes = '';
166+
for (const { 0: open } of codes) {
167+
openCodes += escapeStyleCode(open);
168+
}
169+
170+
// Process the text to handle nested styles
171+
const processedText = text.replace(endingRegex, (match, offset) => {
172+
// Check if there's more content after this reset
173+
if (offset + match.length < text.length) {
174+
for (const { 0: open } of codes) {
175+
// Check if this is a foreground color (30-37, 90-97) or Background color (40-47, 100-107)
176+
if ((open >= 30 && open <= 37) || (open >= 90 && open <= 97) ||
177+
(open >= 40 && open <= 47) || (open >= 100 && open <= 107)) {
178+
return escapeStyleCode(open);
179+
}
180+
}
181+
}
182+
return match;
183+
});
184+
185+
// Build closing codes in reverse order
186+
let closeCodes = '';
187+
for (let i = codes.length - 1; i >= 0; i--) {
188+
closeCodes += escapeStyleCode(codes[i][1]);
152189
}
153190

154-
return skipColorize ? text : `${left}${text}${right}`;
191+
return `${openCodes}${processedText}${closeCodes}`;
155192
}
156193

157194
/**

test/parallel/test-util-styletext.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ assert.strictEqual(
4646
'\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m',
4747
);
4848

49+
assert.strictEqual(
50+
util.styleText('red', 'A' + util.styleText('blue', 'B') + 'C'),
51+
'\u001b[31mA\u001b[34mB\u001b[31mC\u001b[39m',
52+
);
53+
4954
assert.strictEqual(
5055
util.styleText(['bold', 'red'], 'test', { validateStream: false }),
5156
util.styleText(

0 commit comments

Comments
 (0)