@@ -5,10 +5,13 @@ import (
55 "io"
66 "reflect"
77 "strings"
8- "text/tabwriter"
98 "time"
9+
10+ "github.com/mattn/go-runewidth"
1011)
1112
13+ const columnGap = 2
14+
1215// TablePrinter prints data as aligned tables.
1316type TablePrinter struct {
1417 w io.Writer
@@ -18,19 +21,14 @@ type TablePrinter struct {
1821func (p * TablePrinter ) Print (data any , columns []Column ) error {
1922 items := toSlice (data )
2023
21- tw := tabwriter .NewWriter (p .w , 0 , 4 , 2 , ' ' , 0 )
22-
23- // Header
24+ // Build all cell values and compute column widths using display width.
2425 headers := make ([]string , len (columns ))
2526 for i , col := range columns {
2627 headers [i ] = col .Header
2728 }
28- if _ , err := fmt .Fprintln (tw , strings .Join (headers , "\t " )); err != nil {
29- return err
30- }
3129
32- // Rows
33- for _ , item := range items {
30+ rows := make ([][] string , len ( items ))
31+ for r , item := range items {
3432 vals := make ([]string , len (columns ))
3533 for i , col := range columns {
3634 v := col .Field (item )
@@ -39,23 +37,57 @@ func (p *TablePrinter) Print(data any, columns []Column) error {
3937 }
4038 vals [i ] = v
4139 }
42- if _ , err := fmt .Fprintln (tw , strings .Join (vals , "\t " )); err != nil {
40+ rows [r ] = vals
41+ }
42+
43+ // Compute max display width per column.
44+ colWidths := make ([]int , len (columns ))
45+ for i , h := range headers {
46+ colWidths [i ] = runewidth .StringWidth (h )
47+ }
48+ for _ , row := range rows {
49+ for i , v := range row {
50+ if w := runewidth .StringWidth (v ); w > colWidths [i ] {
51+ colWidths [i ] = w
52+ }
53+ }
54+ }
55+
56+ // Print header.
57+ if err := p .printRow (headers , colWidths ); err != nil {
58+ return err
59+ }
60+ // Print data rows.
61+ for _ , row := range rows {
62+ if err := p .printRow (row , colWidths ); err != nil {
4363 return err
4464 }
4565 }
66+ return nil
67+ }
4668
47- return tw .Flush ()
69+ func (p * TablePrinter ) printRow (cells []string , colWidths []int ) error {
70+ var sb strings.Builder
71+ for i , cell := range cells {
72+ sb .WriteString (cell )
73+ if i < len (cells )- 1 {
74+ pad := colWidths [i ] - runewidth .StringWidth (cell ) + columnGap
75+ sb .WriteString (strings .Repeat (" " , pad ))
76+ }
77+ }
78+ _ , err := fmt .Fprintln (p .w , sb .String ())
79+ return err
4880}
4981
50- // Truncate shortens s to maxLen, appending "..." if truncated.
82+ // Truncate shortens s to maxLen display columns , appending "..." if truncated.
5183func Truncate (s string , maxLen int ) string {
52- if maxLen <= 0 || len (s ) <= maxLen {
84+ if maxLen <= 0 || runewidth . StringWidth (s ) <= maxLen {
5385 return s
5486 }
5587 if maxLen <= 3 {
56- return s [: maxLen ]
88+ return runewidth . Truncate ( s , maxLen , "" )
5789 }
58- return s [: maxLen - 3 ] + "..."
90+ return runewidth . Truncate ( s , maxLen , "..." )
5991}
6092
6193// FormatTime formats a unix timestamp as local time.
0 commit comments