X Tutup
Skip to content

Commit b34fe81

Browse files
authored
Merge pull request cli#1677 from cli/fix-issue-list-emoji-bug
Fix issue listing misalignment
2 parents 5a8df47 + e5466c0 commit b34fe81

File tree

4 files changed

+68
-48
lines changed

4 files changed

+68
-48
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ require (
1414
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
1515
github.com/mattn/go-colorable v0.1.7
1616
github.com/mattn/go-isatty v0.0.12
17+
github.com/mattn/go-runewidth v0.0.9
1718
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
1819
github.com/mitchellh/go-homedir v1.1.0
20+
github.com/rivo/uniseg v0.1.0
1921
github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5
2022
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
2123
github.com/spf13/cobra v1.0.0
2224
github.com/spf13/pflag v1.0.5
2325
github.com/stretchr/testify v1.6.1
2426
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
25-
golang.org/x/text v0.3.3
27+
golang.org/x/text v0.3.3 // indirect
2628
gopkg.in/yaml.v2 v2.2.8 // indirect
2729
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
2830
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
151151
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
152152
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
153153
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
154+
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
155+
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
154156
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
155157
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
156158
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=

pkg/text/truncate.go

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,75 @@
11
package text
22

33
import (
4-
"golang.org/x/text/width"
4+
runewidth "github.com/mattn/go-runewidth"
5+
"github.com/rivo/uniseg"
6+
)
7+
8+
const (
9+
ellipsisWidth = 3
10+
minWidthForEllipsis = 5
511
)
612

713
// DisplayWidth calculates what the rendered width of a string may be
814
func DisplayWidth(s string) int {
15+
g := uniseg.NewGraphemes(s)
916
w := 0
10-
for _, r := range s {
11-
w += runeDisplayWidth(r)
17+
for g.Next() {
18+
w += graphemeWidth(g)
1219
}
1320
return w
1421
}
1522

16-
const (
17-
ellipsisWidth = 3
18-
minWidthForEllipsis = 5
19-
)
20-
2123
// Truncate shortens a string to fit the maximum display width
22-
func Truncate(max int, s string) string {
24+
func Truncate(maxWidth int, s string) string {
2325
w := DisplayWidth(s)
24-
if w <= max {
26+
if w <= maxWidth {
2527
return s
2628
}
2729

2830
useEllipsis := false
29-
if max >= minWidthForEllipsis {
31+
if maxWidth >= minWidthForEllipsis {
3032
useEllipsis = true
31-
max -= ellipsisWidth
33+
maxWidth -= ellipsisWidth
3234
}
3335

34-
cw := 0
35-
ri := 0
36-
for _, r := range s {
37-
rw := runeDisplayWidth(r)
38-
if cw+rw > max {
36+
g := uniseg.NewGraphemes(s)
37+
r := ""
38+
rWidth := 0
39+
for {
40+
g.Next()
41+
gWidth := graphemeWidth(g)
42+
43+
if rWidth+gWidth <= maxWidth {
44+
r += g.Str()
45+
rWidth += gWidth
46+
continue
47+
} else {
3948
break
4049
}
41-
cw += rw
42-
ri++
4350
}
4451

45-
res := string([]rune(s)[:ri])
4652
if useEllipsis {
47-
res += "..."
53+
r += "..."
4854
}
49-
if cw < max {
50-
// compensate if truncating a wide character left an odd space
51-
res += " "
55+
56+
if rWidth < maxWidth {
57+
r += " "
5258
}
53-
return res
54-
}
5559

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
60+
return r
6761
}
6862

69-
func runeDisplayWidth(r rune) int {
70-
if w, ok := runeDisplayWidthOverrides[r]; ok {
71-
return w
72-
}
73-
74-
switch width.LookupRune(r).Kind() {
75-
case width.EastAsianWide, width.EastAsianAmbiguous, width.EastAsianFullwidth:
76-
return 2
77-
default:
78-
return 1
63+
// graphemeWidth calculates what the rendered width of a grapheme may be
64+
func graphemeWidth(g *uniseg.Graphemes) int {
65+
// If grapheme spans one rune use that rune width
66+
// If grapheme spans multiple runes use the first non-zero rune width
67+
w := 0
68+
for _, r := range g.Runes() {
69+
w = runewidth.RuneWidth(r)
70+
if w > 0 {
71+
break
72+
}
7973
}
74+
return w
8075
}

pkg/text/truncate_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ func TestTruncate(t *testing.T) {
6262
},
6363
want: "a프로젝... ",
6464
},
65+
{
66+
name: "Emoji",
67+
args: args{
68+
max: 11,
69+
s: "💡💡💡💡💡💡💡💡💡💡💡💡",
70+
},
71+
want: "💡💡💡💡...",
72+
},
73+
{
74+
name: "Accented characters",
75+
args: args{
76+
max: 11,
77+
s: "é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́",
78+
},
79+
want: "é́́é́́é́́é́́é́́é́́é́́é́́...",
80+
},
6581
}
6682
for _, tt := range tests {
6783
t.Run(tt.name, func(t *testing.T) {
@@ -128,6 +144,11 @@ func TestDisplayWidth(t *testing.T) {
128144
text: `👍`,
129145
want: 2,
130146
},
147+
{
148+
name: "accent character",
149+
text: `é́́`,
150+
want: 1,
151+
},
131152
}
132153
for _, tt := range tests {
133154
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)
X Tutup