Use character display width for table padding

Using len(...) for table cell padding produced wrong results for unicode
chracters leading to misaligned tables. Implementation changed to take
the actual terminal display width into consideration.
This commit is contained in:
Florian Thoma 2024-06-05 09:33:15 +02:00
parent 660679c2f6
commit e9de9684f4
No known key found for this signature in database
GPG Key ID: 2146AF34C97213F9
4 changed files with 51 additions and 8 deletions

View File

@ -8,6 +8,8 @@ import (
"math/bits"
"strconv"
"time"
"golang.org/x/text/width"
)
func FormatBytes(c uint64) string {
@ -105,3 +107,24 @@ func ToJSONString(status interface{}) string {
}
return buf.String()
}
// TerminalDisplayWidth returns the number of terminal cells needed to display s
func TerminalDisplayWidth(s string) int {
width := 0
for _, r := range s {
width += terminalDisplayRuneWidth(r)
}
return width
}
func terminalDisplayRuneWidth(r rune) int {
switch width.LookupRune(r).Kind() {
case width.EastAsianWide, width.EastAsianFullwidth:
return 2
case width.EastAsianNarrow, width.EastAsianHalfwidth, width.EastAsianAmbiguous, width.Neutral:
return 1
default:
return 0
}
}

View File

@ -84,3 +84,21 @@ func TestParseBytesInvalid(t *testing.T) {
test.Equals(t, int64(0), v)
}
}
func TestTerminalDisplayWidth(t *testing.T) {
for _, c := range []struct {
input string
want int
}{
{"foo", 3},
{"aéb", 3},
{"ab", 3},
{"ab", 3},
{"aあb", 4},
} {
if got := TerminalDisplayWidth(c.input); got != c.want {
t.Errorf("wrong display width for '%s', want %d, got %d", c.input, c.want, got)
}
}
}

View File

@ -6,6 +6,8 @@ import (
"strings"
"text/template"
"github.com/restic/restic/internal/ui"
)
// Table contains data for a table to be printed.
@ -89,7 +91,7 @@ func printLine(w io.Writer, print func(io.Writer, string) error, sep string, dat
}
// apply padding
pad := widths[fieldNum] - len(v)
pad := widths[fieldNum] - ui.TerminalDisplayWidth(v)
if pad > 0 {
v += strings.Repeat(" ", pad)
}
@ -139,16 +141,16 @@ func (t *Table) Write(w io.Writer) error {
columnWidths := make([]int, columns)
for i, desc := range t.columns {
for _, line := range strings.Split(desc, "\n") {
if columnWidths[i] < len(line) {
columnWidths[i] = len(desc)
if columnWidths[i] < ui.TerminalDisplayWidth(line) {
columnWidths[i] = ui.TerminalDisplayWidth(desc)
}
}
}
for _, line := range lines {
for i, content := range line {
for _, l := range strings.Split(content, "\n") {
if columnWidths[i] < len(l) {
columnWidths[i] = len(l)
if columnWidths[i] < ui.TerminalDisplayWidth(l) {
columnWidths[i] = ui.TerminalDisplayWidth(l)
}
}
}
@ -159,7 +161,7 @@ func (t *Table) Write(w io.Writer) error {
for _, width := range columnWidths {
totalWidth += width
}
totalWidth += (columns - 1) * len(t.CellSeparator)
totalWidth += (columns - 1) * ui.TerminalDisplayWidth(t.CellSeparator)
// write header
if len(t.columns) > 0 {

View File

@ -126,7 +126,7 @@ foo 2018-08-19 22:22:22 xxx other /home/user/other
Time string
Tags, Dirs []string
}
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"work", "go"}, []string{"/home/user/work", "/home/user/go"}})
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"work", "gos"}, []string{"/home/user/work", "/home/user/go"}})
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other"}, []string{"/home/user/other"}})
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other", "bar"}, []string{"/home/user/other"}})
return table
@ -135,7 +135,7 @@ foo 2018-08-19 22:22:22 xxx other /home/user/other
host name time zz tags dirs
------------------------------------------------------------
foo 2018-08-19 22:22:22 xxx work /home/user/work
go /home/user/go
gos /home/user/go
foo 2018-08-19 22:22:22 xxx other /home/user/other
foo 2018-08-19 22:22:22 xxx other /home/user/other
bar