X Tutup
Skip to content

Commit ed15beb

Browse files
committed
Ensure that table printer fills the full width of the terminal
Sometimes, due to rounding errors, after calculating the width of each column in a table, the sum of all columns would be shorter that the total available width in the terminal. This reimplements the elastic column resizing algorithm to ensure that all available space has been filled. As a bonus fix, columns that contain URLs are never truncated.
1 parent eddd8f0 commit ed15beb

File tree

2 files changed

+107
-32
lines changed

2 files changed

+107
-32
lines changed

pkg/cmd/gist/list/list_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,12 @@ func Test_listRun(t *testing.T) {
177177
)),
178178
)
179179
},
180-
wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n4567890123 1 file public about 6 hours ago\n2345678901 tea leaves thwart... 2 files secret about 6 hours ago\n3456789012 short desc 11 files secret about 6 hours ago\n",
180+
wantOut: heredoc.Doc(`
181+
1234567890 cool.txt 1 file public about 6 hours ago
182+
4567890123 1 file public about 6 hours ago
183+
2345678901 tea leaves thwart those who ... 2 files secret about 6 hours ago
184+
3456789012 short desc 11 files secret about 6 hours ago
185+
`),
181186
},
182187
{
183188
name: "with public filter",
@@ -206,7 +211,10 @@ func Test_listRun(t *testing.T) {
206211
)),
207212
)
208213
},
209-
wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n4567890123 1 file public about 6 hours ago\n",
214+
wantOut: heredoc.Doc(`
215+
1234567890 cool.txt 1 file public about 6 hours ago
216+
4567890123 1 file public about 6 hours ago
217+
`),
210218
},
211219
{
212220
name: "with secret filter",
@@ -250,7 +258,10 @@ func Test_listRun(t *testing.T) {
250258
)),
251259
)
252260
},
253-
wantOut: "2345678901 tea leaves thwart... 2 files secret about 6 hours ago\n3456789012 short desc 11 files secret about 6 hours ago\n",
261+
wantOut: heredoc.Doc(`
262+
2345678901 tea leaves thwart those who ... 2 files secret about 6 hours ago
263+
3456789012 short desc 11 files secret about 6 hours ago
264+
`),
254265
},
255266
{
256267
name: "with limit",

utils/table_printer.go

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"fmt"
55
"io"
6+
"sort"
67
"strings"
78

89
"github.com/cli/cli/pkg/iostreams"
@@ -34,6 +35,10 @@ type tableField struct {
3435
ColorFunc func(string) string
3536
}
3637

38+
func (f *tableField) DisplayWidth() int {
39+
return text.DisplayWidth(f.Text)
40+
}
41+
3742
type ttyTablePrinter struct {
3843
out io.Writer
3944
maxWidth int
@@ -69,35 +74,9 @@ func (t *ttyTablePrinter) Render() error {
6974
return nil
7075
}
7176

72-
numCols := len(t.rows[0])
73-
colWidths := make([]int, numCols)
74-
// measure maximum content width per column
75-
for _, row := range t.rows {
76-
for col, field := range row {
77-
textLen := text.DisplayWidth(field.Text)
78-
if textLen > colWidths[col] {
79-
colWidths[col] = textLen
80-
}
81-
}
82-
}
83-
8477
delim := " "
85-
availWidth := t.maxWidth - colWidths[0] - ((numCols - 1) * len(delim))
86-
// add extra space from columns that are already narrower than threshold
87-
for col := 1; col < numCols; col++ {
88-
availColWidth := availWidth / (numCols - 1)
89-
if extra := availColWidth - colWidths[col]; extra > 0 {
90-
availWidth += extra
91-
}
92-
}
93-
// cap all but first column to fit available terminal width
94-
// TODO: support weighted instead of even redistribution
95-
for col := 1; col < numCols; col++ {
96-
availColWidth := availWidth / (numCols - 1)
97-
if colWidths[col] > availColWidth {
98-
colWidths[col] = availColWidth
99-
}
100-
}
78+
numCols := len(t.rows[0])
79+
colWidths := t.calculateColumnWidths(len(delim))
10180

10281
for _, row := range t.rows {
10382
for col, field := range row {
@@ -110,7 +89,7 @@ func (t *ttyTablePrinter) Render() error {
11089
truncVal := field.TruncateFunc(colWidths[col], field.Text)
11190
if col < numCols-1 {
11291
// pad value with spaces on the right
113-
if padWidth := colWidths[col] - text.DisplayWidth(field.Text); padWidth > 0 {
92+
if padWidth := colWidths[col] - field.DisplayWidth(); padWidth > 0 {
11493
truncVal += strings.Repeat(" ", padWidth)
11594
}
11695
}
@@ -132,6 +111,91 @@ func (t *ttyTablePrinter) Render() error {
132111
return nil
133112
}
134113

114+
func (t *ttyTablePrinter) calculateColumnWidths(delimSize int) []int {
115+
numCols := len(t.rows[0])
116+
allColWidths := make([][]int, numCols)
117+
for _, row := range t.rows {
118+
for col, field := range row {
119+
allColWidths[col] = append(allColWidths[col], field.DisplayWidth())
120+
}
121+
}
122+
123+
// calculate max & median content width per column
124+
maxColWidths := make([]int, numCols)
125+
// medianColWidth := make([]int, numCols)
126+
for col := 0; col < numCols; col++ {
127+
widths := allColWidths[col]
128+
sort.Ints(widths)
129+
maxColWidths[col] = widths[len(widths)-1]
130+
// medianColWidth[col] = widths[(len(widths)+1)/2]
131+
}
132+
133+
colWidths := make([]int, numCols)
134+
// never truncate the first column
135+
colWidths[0] = maxColWidths[0]
136+
// never truncate the last column if it contains URLs
137+
if strings.HasPrefix(t.rows[0][numCols-1].Text, "https://") {
138+
colWidths[numCols-1] = maxColWidths[numCols-1]
139+
}
140+
141+
availWidth := func() int {
142+
setWidths := 0
143+
for col := 0; col < numCols; col++ {
144+
setWidths += colWidths[col]
145+
}
146+
return t.maxWidth - delimSize*(numCols-1) - setWidths
147+
}
148+
numFixedCols := func() int {
149+
fixedCols := 0
150+
for col := 0; col < numCols; col++ {
151+
if colWidths[col] > 0 {
152+
fixedCols++
153+
}
154+
}
155+
return fixedCols
156+
}
157+
158+
// set the widths of short columns
159+
if w := availWidth(); w > 0 {
160+
if numFlexColumns := numCols - numFixedCols(); numFlexColumns > 0 {
161+
perColumn := w / numFlexColumns
162+
for col := 0; col < numCols; col++ {
163+
if max := maxColWidths[col]; max < perColumn {
164+
colWidths[col] = max
165+
}
166+
}
167+
}
168+
}
169+
170+
firstFlexCol := -1
171+
// truncate long columns to the remaining available width
172+
if numFlexColumns := numCols - numFixedCols(); numFlexColumns > 0 {
173+
perColumn := availWidth() / numFlexColumns
174+
for col := 0; col < numCols; col++ {
175+
if colWidths[col] == 0 {
176+
if firstFlexCol == -1 {
177+
firstFlexCol = col
178+
}
179+
if max := maxColWidths[col]; max < perColumn {
180+
colWidths[col] = max
181+
} else {
182+
colWidths[col] = perColumn
183+
}
184+
}
185+
}
186+
}
187+
188+
// add remainder to the first flex column
189+
if w := availWidth(); w > 0 && firstFlexCol > -1 {
190+
colWidths[firstFlexCol] += w
191+
if max := maxColWidths[firstFlexCol]; max < colWidths[firstFlexCol] {
192+
colWidths[firstFlexCol] = max
193+
}
194+
}
195+
196+
return colWidths
197+
}
198+
135199
type tsvTablePrinter struct {
136200
out io.Writer
137201
currentCol int

0 commit comments

Comments
 (0)
X Tutup