Merge pull request #1677 from cli/fix-issue-list-emoji-bug

Fix issue listing misalignment
This commit is contained in:
Sam 2020-09-12 19:07:56 +02:00 committed by GitHub
commit b34fe8123a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 53 deletions

4
go.mod
View file

@ -14,15 +14,17 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-colorable v0.1.7
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.9
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/mitchellh/go-homedir v1.1.0
github.com/rivo/uniseg v0.1.0
github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/text v0.3.3
golang.org/x/text v0.3.3 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
)

2
go.sum
View file

@ -151,6 +151,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=

View file

@ -1,80 +1,75 @@
package text
import (
"golang.org/x/text/width"
runewidth "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// DisplayWidth calculates what the rendered width of a string may be
func DisplayWidth(s string) int {
w := 0
for _, r := range s {
w += runeDisplayWidth(r)
}
return w
}
const (
ellipsisWidth = 3
minWidthForEllipsis = 5
)
// DisplayWidth calculates what the rendered width of a string may be
func DisplayWidth(s string) int {
g := uniseg.NewGraphemes(s)
w := 0
for g.Next() {
w += graphemeWidth(g)
}
return w
}
// Truncate shortens a string to fit the maximum display width
func Truncate(max int, s string) string {
func Truncate(maxWidth int, s string) string {
w := DisplayWidth(s)
if w <= max {
if w <= maxWidth {
return s
}
useEllipsis := false
if max >= minWidthForEllipsis {
if maxWidth >= minWidthForEllipsis {
useEllipsis = true
max -= ellipsisWidth
maxWidth -= ellipsisWidth
}
cw := 0
ri := 0
for _, r := range s {
rw := runeDisplayWidth(r)
if cw+rw > max {
g := uniseg.NewGraphemes(s)
r := ""
rWidth := 0
for {
g.Next()
gWidth := graphemeWidth(g)
if rWidth+gWidth <= maxWidth {
r += g.Str()
rWidth += gWidth
continue
} else {
break
}
cw += rw
ri++
}
res := string([]rune(s)[:ri])
if useEllipsis {
res += "..."
}
if cw < max {
// compensate if truncating a wide character left an odd space
res += " "
}
return res
}
var runeDisplayWidthOverrides = map[rune]int{
'“': 1,
'”': 1,
'': 1,
'': 1,
'': 1, // en dash
'—': 1, // em dash
'→': 1,
'…': 1,
'•': 1, // bullet
'·': 1, // middle dot
}
func runeDisplayWidth(r rune) int {
if w, ok := runeDisplayWidthOverrides[r]; ok {
return w
r += "..."
}
switch width.LookupRune(r).Kind() {
case width.EastAsianWide, width.EastAsianAmbiguous, width.EastAsianFullwidth:
return 2
default:
return 1
if rWidth < maxWidth {
r += " "
}
return r
}
// graphemeWidth calculates what the rendered width of a grapheme may be
func graphemeWidth(g *uniseg.Graphemes) int {
// If grapheme spans one rune use that rune width
// If grapheme spans multiple runes use the first non-zero rune width
w := 0
for _, r := range g.Runes() {
w = runewidth.RuneWidth(r)
if w > 0 {
break
}
}
return w
}

View file

@ -62,6 +62,22 @@ func TestTruncate(t *testing.T) {
},
want: "a프로젝... ",
},
{
name: "Emoji",
args: args{
max: 11,
s: "💡💡💡💡💡💡💡💡💡💡💡💡",
},
want: "💡💡💡💡...",
},
{
name: "Accented characters",
args: args{
max: 11,
s: "é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́é́́",
},
want: "é́́é́́é́́é́́é́́é́́é́́é́́...",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -128,6 +144,11 @@ func TestDisplayWidth(t *testing.T) {
text: `👍`,
want: 2,
},
{
name: "accent character",
text: `é́́`,
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {