@@ -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+
808912function 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+
11991323function 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
12451357module . exports = {
0 commit comments