@@ -785,8 +785,35 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
785
785
}
786
786
}
787
787
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
+ }
790
817
791
818
const res = reduceToSingleString ( ctx , output , base , braces , combine ) ;
792
819
const budget = ctx . budget [ ctx . indentationLvl ] || 0 ;
@@ -805,6 +832,83 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
805
832
return res ;
806
833
}
807
834
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
+
808
912
function handleMaxCallStackSize ( ctx , err , constructor , tag , indentationLvl ) {
809
913
if ( isStackOverflowError ( err ) ) {
810
914
ctx . seen . pop ( ) ;
@@ -1196,50 +1300,58 @@ function formatProperty(ctx, value, recurseTimes, key, type) {
1196
1300
return `${ name } :${ extra } ${ str } ` ;
1197
1301
}
1198
1302
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
+
1199
1323
function reduceToSingleString ( ctx , output , base , braces , combine = false ) {
1200
- const breakLength = ctx . breakLength ;
1201
- let i = 0 ;
1202
1324
if ( ctx . compact !== true ) {
1203
1325
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 ] ;
1212
1334
}
1213
1335
}
1336
+ // Line up each entry on an individual line.
1214
1337
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 ] } ` ;
1221
1340
}
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 ] ;
1234
1346
}
1347
+ const indentation = ' ' . repeat ( ctx . indentationLvl ) ;
1235
1348
// If the opening "brace" is too large, like in the case of "Set {",
1236
1349
// we need to force the first item to be on the next line or the
1237
1350
// items will not line up correctly.
1238
- const indentation = ' ' . repeat ( ctx . indentationLvl ) ;
1239
1351
const ln = base === '' && braces [ 0 ] . length === 1 ?
1240
1352
' ' : `${ 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 ] } ` ;
1243
1355
}
1244
1356
1245
1357
module . exports = {
0 commit comments