Skip to content
This repository was archived by the owner on Apr 19, 2024. It is now read-only.

Commit b54ce38

Browse files
authored
Remove ANSI formatting before measuring string width (#462)
1 parent 8fe20ce commit b54ce38

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
2121
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
2222
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
2323
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
24-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
2524
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2625
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
2726
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

terminal/runereader.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,19 +377,42 @@ func (rr *RuneReader) ReadLineWithDefault(mask rune, d []rune, onRunes ...OnRune
377377
}
378378
}
379379

380+
// runeWidth returns the number of columns spanned by a rune when printed to the terminal
380381
func runeWidth(r rune) int {
381382
switch width.LookupRune(r).Kind() {
382383
case width.EastAsianWide, width.EastAsianFullwidth:
383384
return 2
384385
}
386+
387+
if !unicode.IsPrint(r) {
388+
return 0
389+
}
385390
return 1
386391
}
387392

393+
// isAnsiMarker returns if a rune denotes the start of an ANSI sequence
394+
func isAnsiMarker(r rune) bool {
395+
return r == '\x1B'
396+
}
397+
398+
// isAnsiTerminator returns if a rune denotes the end of an ANSI sequence
399+
func isAnsiTerminator(r rune) bool {
400+
return (r >= 0x40 && r <= 0x5a) || (r == 0x5e) || (r >= 0x60 && r <= 0x7e)
401+
}
402+
403+
// StringWidth returns the visible width of a string when printed to the terminal
388404
func StringWidth(str string) int {
389405
w := 0
406+
ansi := false
407+
390408
rs := []rune(str)
391409
for _, r := range rs {
392-
w += runeWidth(r)
410+
// increase width only when outside of ANSI escape sequences
411+
if ansi || isAnsiMarker(r) {
412+
ansi = !isAnsiTerminator(r)
413+
} else {
414+
w += runeWidth(r)
415+
}
393416
}
394417
return w
395418
}

terminal/runereader_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package terminal
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestRuneWidthInvisible(t *testing.T) {
8+
var example rune = '⁣'
9+
expected := 0
10+
actual := runeWidth(example)
11+
if actual != expected {
12+
t.Errorf("Expected '%c' to have width %d, found %d", example, expected, actual)
13+
}
14+
}
15+
16+
func TestRuneWidthNormal(t *testing.T) {
17+
var example rune = 'a'
18+
expected := 1
19+
actual := runeWidth(example)
20+
if actual != expected {
21+
t.Errorf("Expected '%c' to have width %d, found %d", example, expected, actual)
22+
}
23+
}
24+
25+
func TestRuneWidthWide(t *testing.T) {
26+
var example rune = '错'
27+
expected := 2
28+
actual := runeWidth(example)
29+
if actual != expected {
30+
t.Errorf("Expected '%c' to have width %d, found %d", example, expected, actual)
31+
}
32+
}
33+
34+
func TestStringWidthEmpty(t *testing.T) {
35+
example := ""
36+
expected := 0
37+
actual := StringWidth(example)
38+
if actual != expected {
39+
t.Errorf("Expected '%s' to have width %d, found %d", example, expected, actual)
40+
}
41+
}
42+
43+
func TestStringWidthNormal(t *testing.T) {
44+
example := "Green"
45+
expected := 5
46+
actual := StringWidth(example)
47+
if actual != expected {
48+
t.Errorf("Expected '%s' to have width %d, found %d", example, expected, actual)
49+
}
50+
}
51+
52+
func TestStringWidthFormat(t *testing.T) {
53+
example := "\033[31mRed\033[0m"
54+
expected := 3
55+
actual := StringWidth(example)
56+
if actual != expected {
57+
t.Errorf("Expected '%s' to have width %d, found %d", example, expected, actual)
58+
}
59+
60+
example = "\033[1;34mbold\033[21mblue\033[0m"
61+
expected = 8
62+
actual = StringWidth(example)
63+
if actual != expected {
64+
t.Errorf("Expected '%s' to have width %d, found %d", example, expected, actual)
65+
}
66+
}

0 commit comments

Comments
 (0)