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 6828fbb

Browse filesBrowse files
BridgeARaddaleax
authored andcommitted
util: group array elements together
When using `util.inspect()` with `compact` mode set to a number, all array entries exceeding 6 are going to be grouped together into logical parts. PR-URL: #26269 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 4500ed8 commit 6828fbb
Copy full SHA for 6828fbb

File tree

Expand file treeCollapse file tree

3 files changed

+341
-36
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+341
-36
lines changed
Open diff view settings
Collapse file

‎doc/api/util.md‎

Copy file name to clipboardExpand all lines: doc/api/util.md
+3-3Lines changed: 3 additions & 3 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,9 @@ changes:
451451
to be displayed on a new line. It will also add new lines to text that is
452452
longer than `breakLength`. If set to a number, the most `n` inner elements
453453
are united on a single line as long as all properties fit into
454-
`breakLength`. Note that no text will be reduced below 16 characters, no
455-
matter the `breakLength` size. For more information, see the example below.
456-
**Default:** `true`.
454+
`breakLength`. Short array elements are also grouped together. Note that no
455+
text will be reduced below 16 characters, no matter the `breakLength` size.
456+
For more information, see the example below. **Default:** `true`.
457457
* `sorted` {boolean|Function} If set to `true` or a function, all properties
458458
of an object, and `Set` and `Map` entries are sorted in the resulting
459459
string. If set to `true` the [default sort][] is used. If set to a function,
Collapse file

‎lib/internal/util/inspect.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/inspect.js
+145-33Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -785,8 +785,35 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
785785
}
786786
}
787787

788-
const combine = typeof ctx.compact === 'number' &&
789-
ctx.currentDepth - recurseTimes < ctx.compact;
788+
let combine = false;
789+
if (typeof ctx.compact === 'number') {
790+
// Memorize the original output length. In case the the output is grouped,
791+
// prevent lining up the entries on a single line.
792+
const entries = output.length;
793+
// Group array elements together if the array contains at least six separate
794+
// entries.
795+
if (extrasType === kArrayExtrasType && output.length > 6) {
796+
output = groupArrayElements(ctx, output);
797+
}
798+
// `ctx.currentDepth` is set to the most inner depth of the currently
799+
// inspected object part while `recurseTimes` is the actual current depth
800+
// that is inspected.
801+
//
802+
// Example:
803+
//
804+
// const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } }
805+
//
806+
// The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max
807+
// depth of 1.
808+
//
809+
// Consolidate all entries of the local most inner depth up to
810+
// `ctx.compact`, as long as the properties are smaller than
811+
// `ctx.breakLength`.
812+
if (ctx.currentDepth - recurseTimes < ctx.compact &&
813+
entries === output.length) {
814+
combine = true;
815+
}
816+
}
790817

791818
const res = reduceToSingleString(ctx, output, base, braces, combine);
792819
const budget = ctx.budget[ctx.indentationLvl] || 0;
@@ -805,6 +832,83 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
805832
return res;
806833
}
807834

835+
function groupArrayElements(ctx, output) {
836+
let totalLength = 0;
837+
let maxLength = 0;
838+
let i = 0;
839+
const dataLen = new Array(output.length);
840+
// Calculate the total length of all output entries and the individual max
841+
// entries length of all output entries. We have to remove colors first,
842+
// otherwise the length would not be calculated properly.
843+
for (; i < output.length; i++) {
844+
const len = ctx.colors ? removeColors(output[i]).length : output[i].length;
845+
dataLen[i] = len;
846+
totalLength += len;
847+
if (maxLength < len)
848+
maxLength = len;
849+
}
850+
// Add two to `maxLength` as we add a single whitespace character plus a comma
851+
// in-between two entries.
852+
const actualMax = maxLength + 2;
853+
// Check if at least three entries fit next to each other and prevent grouping
854+
// of arrays that contains entries of very different length (i.e., if a single
855+
// entry is longer than 1/5 of all other entries combined). Otherwise the
856+
// space in-between small entries would be enormous.
857+
if (actualMax * 3 + ctx.indentationLvl < ctx.breakLength &&
858+
(totalLength / maxLength > 5 || maxLength <= 6)) {
859+
860+
const approxCharHeights = 2.5;
861+
const bias = 1;
862+
// Dynamically check how many columns seem possible.
863+
const columns = Math.min(
864+
// Ideally a square should be drawn. We expect a character to be about 2.5
865+
// times as high as wide. This is the area formula to calculate a square
866+
// which contains n rectangles of size `actualMax * approxCharHeights`.
867+
// Divide that by `actualMax` to receive the correct number of columns.
868+
// The added bias slightly increases the columns for short entries.
869+
Math.round(
870+
Math.sqrt(
871+
approxCharHeights * (actualMax - bias) * output.length
872+
) / (actualMax - bias)
873+
),
874+
// Limit array grouping for small `compact` modes as the user requested
875+
// minimal grouping.
876+
ctx.compact * 3,
877+
// Limit the columns to a maximum of ten.
878+
10
879+
);
880+
// Return with the original output if no grouping should happen.
881+
if (columns <= 1) {
882+
return output;
883+
}
884+
// Calculate the maximum length of all entries that are visible in the first
885+
// column of the group.
886+
const tmp = [];
887+
let firstLineMaxLength = dataLen[0];
888+
for (i = columns; i < dataLen.length; i += columns) {
889+
if (dataLen[i] > firstLineMaxLength)
890+
firstLineMaxLength = dataLen[i];
891+
}
892+
// Each iteration creates a single line of grouped entries.
893+
for (i = 0; i < output.length; i += columns) {
894+
// Calculate extra color padding in case it's active. This has to be done
895+
// line by line as some lines might contain more colors than others.
896+
let colorPadding = output[i].length - dataLen[i];
897+
// Add padding to the first column of the output.
898+
let str = output[i].padStart(firstLineMaxLength + colorPadding, ' ');
899+
// The last lines may contain less entries than columns.
900+
const max = Math.min(i + columns, output.length);
901+
for (var j = i + 1; j < max; j++) {
902+
colorPadding = output[j].length - dataLen[j];
903+
str += `, ${output[j].padStart(maxLength + colorPadding, ' ')}`;
904+
}
905+
tmp.push(str);
906+
}
907+
output = tmp;
908+
}
909+
return output;
910+
}
911+
808912
function handleMaxCallStackSize(ctx, err, constructor, tag, indentationLvl) {
809913
if (isStackOverflowError(err)) {
810914
ctx.seen.pop();
@@ -1196,50 +1300,58 @@ function formatProperty(ctx, value, recurseTimes, key, type) {
11961300
return `${name}:${extra}${str}`;
11971301
}
11981302

1303+
function isBelowBreakLength(ctx, output, start) {
1304+
// Each entry is separated by at least a comma. Thus, we start with a total
1305+
// length of at least `output.length`. In addition, some cases have a
1306+
// whitespace in-between each other that is added to the total as well.
1307+
let totalLength = output.length + start;
1308+
if (totalLength + output.length > ctx.breakLength)
1309+
return false;
1310+
for (var i = 0; i < output.length; i++) {
1311+
if (ctx.colors) {
1312+
totalLength += removeColors(output[i]).length;
1313+
} else {
1314+
totalLength += output[i].length;
1315+
}
1316+
if (totalLength > ctx.breakLength) {
1317+
return false;
1318+
}
1319+
}
1320+
return true;
1321+
}
1322+
11991323
function reduceToSingleString(ctx, output, base, braces, combine = false) {
1200-
const breakLength = ctx.breakLength;
1201-
let i = 0;
12021324
if (ctx.compact !== true) {
12031325
if (combine) {
1204-
const totalLength = output.reduce((sum, cur) => sum + cur.length, 0);
1205-
if (totalLength + output.length * 2 < breakLength) {
1206-
let res = `${base ? `${base} ` : ''}${braces[0]} `;
1207-
for (; i < output.length - 1; i++) {
1208-
res += `${output[i]}, `;
1209-
}
1210-
res += `${output[i]} ${braces[1]}`;
1211-
return res;
1326+
// Line up all entries on a single line in case the entries do not exceed
1327+
// `breakLength`. Add 10 as constant to start next to all other factors
1328+
// that may reduce `breakLength`.
1329+
const start = output.length + ctx.indentationLvl +
1330+
braces[0].length + base.length + 10;
1331+
if (isBelowBreakLength(ctx, output, start)) {
1332+
return `${base ? `${base} ` : ''}${braces[0]} ${join(output, ', ')} ` +
1333+
braces[1];
12121334
}
12131335
}
1336+
// Line up each entry on an individual line.
12141337
const indentation = `\n${' '.repeat(ctx.indentationLvl)}`;
1215-
let res = `${base ? `${base} ` : ''}${braces[0]}${indentation} `;
1216-
for (; i < output.length - 1; i++) {
1217-
res += `${output[i]},${indentation} `;
1218-
}
1219-
res += `${output[i]}${indentation}${braces[1]}`;
1220-
return res;
1338+
return `${base ? `${base} ` : ''}${braces[0]}${indentation} ` +
1339+
`${join(output, `,${indentation} `)}${indentation}${braces[1]}`;
12211340
}
1222-
if (output.length * 2 <= breakLength) {
1223-
let length = 0;
1224-
for (; i < output.length && length <= breakLength; i++) {
1225-
if (ctx.colors) {
1226-
length += removeColors(output[i]).length + 1;
1227-
} else {
1228-
length += output[i].length + 1;
1229-
}
1230-
}
1231-
if (length <= breakLength)
1232-
return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` +
1233-
braces[1];
1341+
// Line up all entries on a single line in case the entries do not exceed
1342+
// `breakLength`.
1343+
if (isBelowBreakLength(ctx, output, 0)) {
1344+
return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` +
1345+
braces[1];
12341346
}
1347+
const indentation = ' '.repeat(ctx.indentationLvl);
12351348
// If the opening "brace" is too large, like in the case of "Set {",
12361349
// we need to force the first item to be on the next line or the
12371350
// items will not line up correctly.
1238-
const indentation = ' '.repeat(ctx.indentationLvl);
12391351
const ln = base === '' && braces[0].length === 1 ?
12401352
' ' : `${base ? ` ${base}` : ''}\n${indentation} `;
1241-
const str = join(output, `,\n${indentation} `);
1242-
return `${braces[0]}${ln}${str} ${braces[1]}`;
1353+
// Line up each entry on an individual line.
1354+
return `${braces[0]}${ln}${join(output, `,\n${indentation} `)} ${braces[1]}`;
12431355
}
12441356

12451357
module.exports = {

0 commit comments

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