Skip to content

Commit 1f86e7c

Browse files
committed
Fix display width of common punctuation characters
These characters get classified as "East Asian Mixed" by Go's `text/width` package, and thus assumed that their printed version occupies a width of 2 characters, whereas they each only occupy one. I'm not sure why they are classified as East Asian, but I did not have the energy to dive into Go's Unicode tables, so here is a workaround based on an exclusion list.
1 parent abf83c0 commit 1f86e7c

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

pkg/text/truncate.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,24 @@ func Truncate(max int, s string) string {
5353
return res
5454
}
5555

56+
var runeDisplayWidthOverrides = map[rune]int{
57+
'“': 1,
58+
'”': 1,
59+
'‘': 1,
60+
'’': 1,
61+
'–': 1, // en dash
62+
'—': 1, // em dash
63+
'→': 1,
64+
'…': 1,
65+
'•': 1, // bullet
66+
'·': 1, // middle dot
67+
}
68+
5669
func runeDisplayWidth(r rune) int {
70+
if w, ok := runeDisplayWidthOverrides[r]; ok {
71+
return w
72+
}
73+
5774
switch width.LookupRune(r).Kind() {
5875
case width.EastAsianWide, width.EastAsianAmbiguous, width.EastAsianFullwidth:
5976
return 2

pkg/text/truncate_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package text
22

3-
import "testing"
3+
import (
4+
"testing"
5+
)
46

57
func TestTruncate(t *testing.T) {
68
type args struct {
@@ -69,3 +71,69 @@ func TestTruncate(t *testing.T) {
6971
})
7072
}
7173
}
74+
75+
func TestDisplayWidth(t *testing.T) {
76+
tests := []struct {
77+
name string
78+
text string
79+
want int
80+
}{
81+
{
82+
name: "check mark",
83+
text: `✓`,
84+
want: 1,
85+
},
86+
{
87+
name: "bullet icon",
88+
text: `•`,
89+
want: 1,
90+
},
91+
{
92+
name: "middle dot",
93+
text: `·`,
94+
want: 1,
95+
},
96+
{
97+
name: "ellipsis",
98+
text: `…`,
99+
want: 1,
100+
},
101+
{
102+
name: "right arrow",
103+
text: `→`,
104+
want: 1,
105+
},
106+
{
107+
name: "smart double quotes",
108+
text: `“”`,
109+
want: 2,
110+
},
111+
{
112+
name: "smart single quotes",
113+
text: `‘’`,
114+
want: 2,
115+
},
116+
{
117+
name: "em dash",
118+
text: `—`,
119+
want: 1,
120+
},
121+
{
122+
name: "en dash",
123+
text: `–`,
124+
want: 1,
125+
},
126+
{
127+
name: "emoji",
128+
text: `👍`,
129+
want: 2,
130+
},
131+
}
132+
for _, tt := range tests {
133+
t.Run(tt.name, func(t *testing.T) {
134+
if got := DisplayWidth(tt.text); got != tt.want {
135+
t.Errorf("DisplayWidth() = %v, want %v", got, tt.want)
136+
}
137+
})
138+
}
139+
}

0 commit comments

Comments
 (0)