@@ -47,166 +47,31 @@ export function render(data: unknown, opts: RenderOptions = {}): void {
4747 }
4848}
4949
50- // ── CJK-aware string width ──
51-
52- function isWideCodePoint ( cp : number ) : boolean {
53- return (
54- ( cp >= 0x4E00 && cp <= 0x9FFF ) || // CJK Unified Ideographs
55- ( cp >= 0x3400 && cp <= 0x4DBF ) || // CJK Extension A
56- ( cp >= 0x20000 && cp <= 0x2A6DF ) || // CJK Extension B
57- ( cp >= 0xF900 && cp <= 0xFAFF ) || // CJK Compatibility Ideographs
58- ( cp >= 0xFF01 && cp <= 0xFF60 ) || // Fullwidth Forms
59- ( cp >= 0xFFE0 && cp <= 0xFFE6 ) || // Fullwidth Signs
60- ( cp >= 0xAC00 && cp <= 0xD7AF ) || // Hangul Syllables
61- ( cp >= 0x3000 && cp <= 0x303F ) || // CJK Symbols
62- ( cp >= 0x3040 && cp <= 0x309F ) || // Hiragana
63- ( cp >= 0x30A0 && cp <= 0x30FF ) // Katakana
64- ) ;
65- }
66-
67- function displayWidth ( str : string ) : number {
68- let w = 0 ;
69- for ( const ch of str ) {
70- w += isWideCodePoint ( ch . codePointAt ( 0 ) ! ) ? 2 : 1 ;
71- }
72- return w ;
73- }
74-
75- function truncateToWidth ( str : string , maxWidth : number ) : string {
76- if ( maxWidth <= 0 || displayWidth ( str ) <= maxWidth ) return str ;
77-
78- const ellipsis = '...' ;
79- const ellipsisWidth = displayWidth ( ellipsis ) ;
80- if ( maxWidth <= ellipsisWidth ) return ellipsis . slice ( 0 , maxWidth ) ;
81-
82- let out = '' ;
83- let width = 0 ;
84- for ( const ch of str ) {
85- const nextWidth = displayWidth ( ch ) ;
86- if ( width + nextWidth + ellipsisWidth > maxWidth ) break ;
87- out += ch ;
88- width += nextWidth ;
89- }
90-
91- return out + ellipsis ;
92- }
93-
94- // ── Table rendering ──
95-
96- // Fits typical date, status, and ID columns without truncation.
97- const SHORT_COL_THRESHOLD = 15 ;
98-
99- const NUMERIC_RE = / ^ - ? [ \d , ] + \. ? \d * $ / ;
100-
10150function renderTable ( data : unknown , opts : RenderOptions ) : void {
10251 const rows = normalizeRows ( data ) ;
10352 if ( ! rows . length ) { console . log ( styleText ( 'dim' , '(no data)' ) ) ; return ; }
10453 const columns = resolveColumns ( rows , opts ) ;
10554
106- if ( rows . length === 1 ) {
107- renderKeyValue ( rows [ 0 ] , columns , opts ) ;
108- return ;
109- }
110-
111- const cells : string [ ] [ ] = rows . map ( row =>
112- columns . map ( c => {
113- const v = ( row as Record < string , unknown > ) [ c ] ;
114- return v === null || v === undefined ? '' : String ( v ) ;
115- } ) ,
116- ) ;
117-
11855 const header = columns . map ( c => capitalize ( c ) ) ;
119- const colCount = columns . length ;
120-
121- // Single pass: measure column widths + detect numeric columns
122- const colContentWidths = header . map ( h => displayWidth ( h ) ) ;
123- const numericCounts = new Array < number > ( colCount ) . fill ( 0 ) ;
124- const totalCounts = new Array < number > ( colCount ) . fill ( 0 ) ;
125-
126- for ( const row of cells ) {
127- for ( let ci = 0 ; ci < colCount ; ci ++ ) {
128- const w = displayWidth ( row [ ci ] ) ;
129- if ( w > colContentWidths [ ci ] ) colContentWidths [ ci ] = w ;
130- const v = row [ ci ] . trim ( ) ;
131- if ( v ) {
132- totalCounts [ ci ] ++ ;
133- if ( NUMERIC_RE . test ( v ) ) numericCounts [ ci ] ++ ;
134- }
135- }
136- }
137-
138- const colAligns : Array < 'left' | 'right' > = columns . map ( ( _ , ci ) =>
139- totalCounts [ ci ] > 0 && numericCounts [ ci ] / totalCounts [ ci ] > 0.8 ? 'right' : 'left' ,
140- ) ;
141-
142- // Calculate column widths to fit terminal.
143- // cli-table3 colWidths includes cell padding (1 space each side).
144- const termWidth = process . stdout . columns || 120 ;
145- // Border chars: '│' between every column + edges = colCount + 1
146- const borderOverhead = colCount + 1 ;
147- const availableWidth = Math . max ( termWidth - borderOverhead , colCount * 5 ) ;
148-
149- let shortTotal = 0 ;
150- const longIndices : number [ ] = [ ] ;
151-
152- for ( let i = 0 ; i < colCount ; i ++ ) {
153- // +2 for cell padding (1 space each side)
154- const padded = colContentWidths [ i ] + 2 ;
155- if ( colContentWidths [ i ] <= SHORT_COL_THRESHOLD ) {
156- colContentWidths [ i ] = padded ;
157- shortTotal += padded ;
158- } else {
159- longIndices . push ( i ) ;
160- }
161- }
162-
163- const remainingWidth = availableWidth - shortTotal ;
164- if ( longIndices . length > 0 ) {
165- const perLong = Math . max ( Math . floor ( remainingWidth / longIndices . length ) , 12 ) ;
166- for ( const i of longIndices ) {
167- colContentWidths [ i ] = Math . min ( colContentWidths [ i ] + 2 , perLong ) ;
168- }
169- }
170-
17156 const table = new Table ( {
17257 head : header . map ( h => styleText ( 'bold' , h ) ) ,
17358 style : { head : [ ] , border : [ ] } ,
174- colWidths : colContentWidths ,
175- colAligns ,
59+ wordWrap : true ,
60+ wrapOnWordBoundary : true ,
17661 } ) ;
17762
178- for ( const row of cells ) {
179- table . push ( row . map ( ( cell , ci ) => truncateToWidth ( cell , colContentWidths [ ci ] - 2 ) ) ) ;
63+ for ( const row of rows ) {
64+ table . push ( columns . map ( c => {
65+ const v = ( row as Record < string , unknown > ) [ c ] ;
66+ return v === null || v === undefined ? '' : String ( v ) ;
67+ } ) ) ;
18068 }
18169
18270 console . log ( ) ;
18371 if ( opts . title ) console . log ( styleText ( 'dim' , ` ${ opts . title } ` ) ) ;
18472 console . log ( table . toString ( ) ) ;
185- printFooter ( rows . length , opts ) ;
186- }
187-
188- function renderKeyValue ( row : Record < string , unknown > , columns : string [ ] , opts : RenderOptions ) : void {
189- const entries = columns . map ( c => ( {
190- key : capitalize ( c ) ,
191- value : row [ c ] === null || row [ c ] === undefined ? '' : String ( row [ c ] ) ,
192- } ) ) ;
193-
194- const maxKeyWidth = Math . max ( ...entries . map ( e => displayWidth ( e . key ) ) ) ;
195-
196- console . log ( ) ;
197- if ( opts . title ) console . log ( styleText ( 'dim' , ` ${ opts . title } ` ) ) ;
198- console . log ( ) ;
199- for ( const { key, value } of entries ) {
200- const padding = ' ' . repeat ( maxKeyWidth - displayWidth ( key ) ) ;
201- console . log ( ` ${ styleText ( 'bold' , key ) } ${ padding } ${ value } ` ) ;
202- }
203- console . log ( ) ;
204- printFooter ( 1 , opts ) ;
205- }
206-
207- function printFooter ( count : number , opts : RenderOptions ) : void {
20873 const footer : string [ ] = [ ] ;
209- footer . push ( `${ count } items` ) ;
74+ footer . push ( `${ rows . length } items` ) ;
21075 if ( opts . elapsed ) footer . push ( `${ opts . elapsed . toFixed ( 1 ) } s` ) ;
21176 if ( opts . source ) footer . push ( opts . source ) ;
21277 if ( opts . footerExtra ) footer . push ( opts . footerExtra ) ;
0 commit comments