@@ -42,7 +42,7 @@ interface PendingActionSummary {
4242 description : string ;
4343}
4444
45- interface ActionTableSnapshot {
45+ export interface ActionTableSnapshot {
4646 completed : CompletedActionSummary [ ] ;
4747 pending : PendingActionSummary [ ] ;
4848 totalDurationMs : number ;
@@ -54,7 +54,9 @@ interface FormattedTableOutput {
5454 lineCount : number ;
5555}
5656
57- const MAX_DESCRIPTION_COLUMN_WIDTH = 64 ;
57+ const MAX_ERROR_DETAIL_COLUMN_WIDTH = 64 ;
58+ const MIN_DESCRIPTION_COLUMN_WIDTH = 16 ;
59+ const DEFAULT_TERMINAL_WIDTH = 120 ;
5860const extendedPictographicRegex = / \p{ Extended_Pictographic} / u;
5961
6062class ActionTelemetryAccumulator {
@@ -212,19 +214,64 @@ function formatDuration(ms: number): string {
212214}
213215
214216function truncate ( value : string , maxLength : number ) : string {
215- if ( value . length <= maxLength ) {
217+ return truncateDisplay ( value , maxLength ) ;
218+ }
219+
220+ function truncateDisplay ( value : string , maxWidth : number ) : string {
221+ if ( maxWidth <= 0 ) {
222+ return '' ;
223+ }
224+
225+ if ( stringDisplayWidth ( value ) <= maxWidth ) {
216226 return value ;
217227 }
218228
219- if ( maxLength <= 1 ) {
220- return value . slice ( 0 , maxLength ) ;
229+ if ( maxWidth <= 3 ) {
230+ let width = 0 ;
231+ let result = '' ;
232+ for ( const symbol of [ ...value ] ) {
233+ const codePoint = symbol . codePointAt ( 0 ) ;
234+ if ( codePoint === undefined ) {
235+ continue ;
236+ }
237+
238+ const charWidth = charDisplayWidth ( codePoint ) ;
239+ if ( width + charWidth > maxWidth ) {
240+ break ;
241+ }
242+ width += charWidth ;
243+ result += symbol ;
244+ if ( width >= maxWidth ) {
245+ break ;
246+ }
247+ }
248+ return result ;
221249 }
222250
223- if ( maxLength <= 3 ) {
224- return value . slice ( 0 , maxLength ) ;
251+ const ellipsis = '...' ;
252+ const ellipsisWidth = stringDisplayWidth ( ellipsis ) ;
253+ const targetWidth = Math . max ( 1 , maxWidth - ellipsisWidth ) ;
254+ let width = 0 ;
255+ let result = '' ;
256+ for ( const symbol of [ ...value ] ) {
257+ const codePoint = symbol . codePointAt ( 0 ) ;
258+ if ( codePoint === undefined ) {
259+ continue ;
260+ }
261+
262+ const charWidth = charDisplayWidth ( codePoint ) ;
263+ if ( width + charWidth > targetWidth ) {
264+ break ;
265+ }
266+ width += charWidth ;
267+ result += symbol ;
268+ }
269+
270+ if ( ! result ) {
271+ return ellipsis ;
225272 }
226273
227- return `${ value . slice ( 0 , maxLength - 3 ) } ... ` ;
274+ return `${ result } ${ ellipsis } ` ;
228275}
229276
230277export function stringDisplayWidth ( value : string ) : number {
@@ -315,22 +362,78 @@ function isFullWidthCodePoint(codePoint: number): boolean {
315362}
316363
317364function padDisplay ( value : string , width : number , alignment : 'left' | 'right' | 'center' = 'left' ) : string {
318- const valueWidth = stringDisplayWidth ( value ) ;
319- if ( valueWidth >= width ) {
320- return value ;
365+ if ( width <= 0 ) {
366+ return '' ;
367+ }
368+
369+ let text = value ;
370+ let valueWidth = stringDisplayWidth ( text ) ;
371+ if ( valueWidth > width ) {
372+ text = truncateDisplay ( text , width ) ;
373+ valueWidth = stringDisplayWidth ( text ) ;
374+ }
375+
376+ if ( valueWidth === width ) {
377+ return text ;
321378 }
322379
323380 const padding = width - valueWidth ;
324381 if ( alignment === 'right' ) {
325- return `${ ' ' . repeat ( padding ) } ${ value } ` ;
382+ return `${ ' ' . repeat ( padding ) } ${ text } ` ;
326383 }
327384
328385 if ( alignment === 'center' ) {
329386 const left = Math . floor ( padding / 2 ) ;
330- return `${ ' ' . repeat ( left ) } ${ value } ${ ' ' . repeat ( padding - left ) } ` ;
387+ return `${ ' ' . repeat ( left ) } ${ text } ${ ' ' . repeat ( padding - left ) } ` ;
331388 }
332389
333- return `${ value } ${ ' ' . repeat ( padding ) } ` ;
390+ return `${ text } ${ ' ' . repeat ( padding ) } ` ;
391+ }
392+
393+ function computeTablePadding ( columnCount : number ) : number {
394+ return columnCount * 3 + 1 ;
395+ }
396+
397+ function computeTableWidth ( columnWidths : Array < number | undefined > ) : number {
398+ let sum = 0 ;
399+ for ( const width of columnWidths ) {
400+ sum += width ?? 0 ;
401+ }
402+ return sum + computeTablePadding ( columnWidths . length ) ;
403+ }
404+
405+ function adjustDescriptionColumnWidth ( columnWidths : Array < number | undefined > , descriptionColumnIndex : number , descriptionHeaderWidth : number , maxWidth ?: number ) : Array < number | undefined > {
406+ if ( maxWidth === undefined || ! Number . isFinite ( maxWidth ) || maxWidth <= 0 ) {
407+ return columnWidths ;
408+ }
409+
410+ const targetWidth = Math . floor ( maxWidth ) ;
411+ const totalWidth = computeTableWidth ( columnWidths ) ;
412+ const paddingWidth = computeTablePadding ( columnWidths . length ) ;
413+
414+ let sumWithoutDescription = 0 ;
415+ columnWidths . forEach ( ( width , index ) => {
416+ if ( index === descriptionColumnIndex ) {
417+ return ;
418+ }
419+ sumWithoutDescription += width ?? 0 ;
420+ } ) ;
421+ const minDescriptionWidth = Math . max ( descriptionHeaderWidth , MIN_DESCRIPTION_COLUMN_WIDTH ) ;
422+ const currentDescriptionWidth = columnWidths [ descriptionColumnIndex ] ?? minDescriptionWidth ;
423+ const availableWidthForDescription = targetWidth - paddingWidth - sumWithoutDescription ;
424+
425+ if ( totalWidth <= targetWidth ) {
426+ columnWidths [ descriptionColumnIndex ] = Math . max ( currentDescriptionWidth , availableWidthForDescription ) ;
427+ return columnWidths ;
428+ }
429+
430+ if ( availableWidthForDescription >= minDescriptionWidth ) {
431+ columnWidths [ descriptionColumnIndex ] = Math . max ( minDescriptionWidth , Math . min ( currentDescriptionWidth , availableWidthForDescription ) ) ;
432+ return columnWidths ;
433+ }
434+
435+ columnWidths [ descriptionColumnIndex ] = minDescriptionWidth ;
436+ return columnWidths ;
334437}
335438
336439function buildBorderLine ( columnWidths : number [ ] , left : string , middle : string , right : string ) : string {
@@ -343,7 +446,7 @@ function buildBorderLine(columnWidths: number[], left: string, middle: string, r
343446 return result ;
344447}
345448
346- function formatActionTimelineTable ( snapshot : ActionTableSnapshot ) : FormattedTableOutput | undefined {
449+ export function formatActionTimelineTable ( snapshot : ActionTableSnapshot , options ?: { maxWidth ?: number } ) : FormattedTableOutput | undefined {
347450 const showErrorsColumn = snapshot . totalErrorCount > 0 ;
348451
349452 interface TableRow {
@@ -358,7 +461,7 @@ function formatActionTimelineTable(snapshot: ActionTableSnapshot): FormattedTabl
358461 snapshot . pending . forEach ( action => {
359462 const row : TableRow = {
360463 status : '⏳' ,
361- description : truncate ( action . description || '' , MAX_DESCRIPTION_COLUMN_WIDTH ) ,
464+ description : action . description ?? '' ,
362465 durationText : '...' ,
363466 } ;
364467 if ( showErrorsColumn ) {
@@ -370,7 +473,7 @@ function formatActionTimelineTable(snapshot: ActionTableSnapshot): FormattedTabl
370473 snapshot . completed . forEach ( action => {
371474 const row : TableRow = {
372475 status : action . errors . length > 0 ? '❌' : '✅' ,
373- description : truncate ( action . description || '' , MAX_DESCRIPTION_COLUMN_WIDTH ) ,
476+ description : action . description ?? '' ,
374477 durationText : formatDuration ( action . durationMs ) ,
375478 } ;
376479 if ( showErrorsColumn ) {
@@ -397,17 +500,29 @@ function formatActionTimelineTable(snapshot: ActionTableSnapshot): FormattedTabl
397500 const durationHeader = 'Duration' ;
398501 const errorsHeader = '# of Errors' ;
399502
400- const statusWidth = Math . max ( stringDisplayWidth ( statusHeader ) , ...rows . map ( row => stringDisplayWidth ( row . status ) ) , stringDisplayWidth ( totalsRow . status ) ) ;
401- const descriptionWidth = Math . max ( stringDisplayWidth ( descriptionHeader ) , ...rows . map ( row => stringDisplayWidth ( row . description ) ) , stringDisplayWidth ( totalsRow . description ) ) ;
402- const durationWidth = Math . max ( stringDisplayWidth ( durationHeader ) , ...rows . map ( row => stringDisplayWidth ( row . durationText ) ) , stringDisplayWidth ( totalsRow . durationText ) ) ;
403- const errorsWidth = showErrorsColumn ? Math . max ( stringDisplayWidth ( errorsHeader ) , ...rows . map ( row => stringDisplayWidth ( row . errorsText ?? '' ) ) , stringDisplayWidth ( totalsRow . errorsText ?? '' ) ) : 0 ;
503+ let statusWidth = Math . max ( stringDisplayWidth ( statusHeader ) , ...rows . map ( row => stringDisplayWidth ( row . status ) ) , stringDisplayWidth ( totalsRow . status ) ) ;
504+ let descriptionWidth = Math . max ( stringDisplayWidth ( descriptionHeader ) , ...rows . map ( row => stringDisplayWidth ( row . description ) ) , stringDisplayWidth ( totalsRow . description ) ) ;
505+ let durationWidth = Math . max ( stringDisplayWidth ( durationHeader ) , ...rows . map ( row => stringDisplayWidth ( row . durationText ) ) , stringDisplayWidth ( totalsRow . durationText ) ) ;
506+ let errorsWidth = showErrorsColumn ? Math . max ( stringDisplayWidth ( errorsHeader ) , ...rows . map ( row => stringDisplayWidth ( row . errorsText ?? '' ) ) , stringDisplayWidth ( totalsRow . errorsText ?? '' ) ) : 0 ;
404507
405- const padStatus = ( value : string ) : string => padDisplay ( value , statusWidth , 'center' ) ;
508+ let columns : Array < number | undefined > = showErrorsColumn
509+ ? [ statusWidth , descriptionWidth , durationWidth , errorsWidth ]
510+ : [ statusWidth , descriptionWidth , durationWidth ] ;
511+
512+ columns = adjustDescriptionColumnWidth ( columns , 1 , stringDisplayWidth ( descriptionHeader ) , options ?. maxWidth ) ;
513+ statusWidth = columns [ 0 ] ?? statusWidth ;
514+ descriptionWidth = columns [ 1 ] ?? descriptionWidth ;
515+ durationWidth = columns [ 2 ] ?? durationWidth ;
516+ if ( showErrorsColumn ) {
517+ errorsWidth = columns [ 3 ] ?? errorsWidth ;
518+ }
406519
407- const columns = showErrorsColumn
520+ const resolvedColumns = showErrorsColumn
408521 ? [ statusWidth , descriptionWidth , durationWidth , errorsWidth ]
409522 : [ statusWidth , descriptionWidth , durationWidth ] ;
410523
524+ const padStatus = ( value : string ) : string => padDisplay ( value , statusWidth , 'center' ) ;
525+
411526 const formatRow = ( row : TableRow ) : string => {
412527 let line = `│ ${ padStatus ( row . status ) } │ ${ padDisplay ( row . description , descriptionWidth ) } │ ${ padDisplay ( row . durationText , durationWidth , 'right' ) } │` ;
413528 if ( showErrorsColumn ) {
@@ -416,13 +531,13 @@ function formatActionTimelineTable(snapshot: ActionTableSnapshot): FormattedTabl
416531 return line ;
417532 } ;
418533
419- const topBorder = buildBorderLine ( columns , '┌' , '┬' , '┐' ) ;
534+ const topBorder = buildBorderLine ( resolvedColumns , '┌' , '┬' , '┐' ) ;
420535 const headerRow = showErrorsColumn
421536 ? `│ ${ padStatus ( statusHeader ) } │ ${ padDisplay ( descriptionHeader , descriptionWidth ) } │ ${ padDisplay ( durationHeader , durationWidth , 'right' ) } │ ${ padDisplay ( errorsHeader , errorsWidth , 'right' ) } │`
422537 : `│ ${ padStatus ( statusHeader ) } │ ${ padDisplay ( descriptionHeader , descriptionWidth ) } │ ${ padDisplay ( durationHeader , durationWidth , 'right' ) } │` ;
423- const headerDivider = buildBorderLine ( columns , '├' , '┼' , '┤' ) ;
424- const totalsDivider = buildBorderLine ( columns , '├' , '┼' , '┤' ) ;
425- const bottomBorder = buildBorderLine ( columns , '└' , '┴' , '┘' ) ;
538+ const headerDivider = buildBorderLine ( resolvedColumns , '├' , '┼' , '┤' ) ;
539+ const totalsDivider = buildBorderLine ( resolvedColumns , '├' , '┼' , '┤' ) ;
540+ const bottomBorder = buildBorderLine ( resolvedColumns , '└' , '┴' , '┘' ) ;
426541
427542 let output = 'Unity Build Timeline\n' ;
428543 output += `${ topBorder } \n` ;
@@ -441,11 +556,11 @@ function formatActionTimelineTable(snapshot: ActionTableSnapshot): FormattedTabl
441556 const errorRows : Array < { description : string ; detail : string } > = [ ] ;
442557 snapshot . completed . forEach ( action => {
443558 if ( action . errors . length === 0 ) { return ; }
444- const description = truncate ( action . description || '' , MAX_DESCRIPTION_COLUMN_WIDTH ) ;
559+ const description = truncate ( action . description || '' , MAX_ERROR_DETAIL_COLUMN_WIDTH ) ;
445560 action . errors . forEach ( err => {
446561 errorRows . push ( {
447562 description,
448- detail : truncate ( err , MAX_DESCRIPTION_COLUMN_WIDTH ) ,
563+ detail : truncate ( err , MAX_ERROR_DETAIL_COLUMN_WIDTH ) ,
449564 } ) ;
450565 } ) ;
451566 } ) ;
@@ -512,7 +627,7 @@ class ActionTableRenderer {
512627 return ;
513628 }
514629
515- const formatted = formatActionTimelineTable ( snapshot ) ;
630+ const formatted = formatActionTimelineTable ( snapshot , { maxWidth : this . getMaxWidth ( ) } ) ;
516631 if ( ! formatted ) {
517632 return ;
518633 }
@@ -537,6 +652,20 @@ class ActionTableRenderer {
537652 process . stdout . write ( '\u001b[J' ) ;
538653 this . lastRenderLineCount = 0 ;
539654 }
655+
656+ private getMaxWidth ( ) : number {
657+ const stdoutColumns = typeof process . stdout . columns === 'number' ? process . stdout . columns : undefined ;
658+ if ( stdoutColumns && stdoutColumns > 0 ) {
659+ return stdoutColumns ;
660+ }
661+
662+ const envColumns = Number ( process . env . COLUMNS ) ;
663+ if ( Number . isFinite ( envColumns ) && envColumns > 0 ) {
664+ return envColumns ;
665+ }
666+
667+ return DEFAULT_TERMINAL_WIDTH ;
668+ }
540669}
541670
542671function toNumeric ( value : unknown ) : number | undefined {
0 commit comments