Skip to content

Commit

Permalink
text: calculate rune widths better; fixes #78 (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t authored Apr 28, 2019
1 parent fba20de commit 71f451d
Show file tree
Hide file tree
Showing 12 changed files with 1,233 additions and 5 deletions.
25 changes: 24 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
name = "github.com/stretchr/testify"
version = "1.2.1"

[[constraint]]
name = "github.com/mattn/go-runewidth"
version = "0.0.4"

[prune]
go-tests = true
unused-packages = true
22 changes: 18 additions & 4 deletions text/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package text
import (
"strings"
"unicode/utf8"

"github.com/mattn/go-runewidth"
)

// Constants
Expand Down Expand Up @@ -39,7 +41,7 @@ func InsertEveryN(str string, runeToInsert rune, n int) string {
}
out.WriteRune(c)
if !isEscSeq {
outLen++
outLen += RuneWidth(c)
}

if isEscSeq && c == EscapeStopRune {
Expand Down Expand Up @@ -68,11 +70,11 @@ func LongestLineLen(str string) int {
}
currLength = 0
} else if !isEscSeq {
currLength++
currLength += RuneWidth(c)
}
}
if currLength > maxLength {
return currLength
maxLength = currLength
}
return maxLength
}
Expand Down Expand Up @@ -125,12 +127,24 @@ func RuneCount(str string) int {
isEscSeq = false
}
} else {
count++
count += RuneWidth(c)
}
}
return count
}

// RuneWidth returns the mostly accurate character-width of the rune. This is
// not 100% accurate as the character width is usually dependant on the
// typeface (font) used in the console/terminal. For ex.:
// RuneWidth('A') == 1
// RuneWidth('ツ') == 2
// RuneWidth('⊙') == 1
// RuneWidth('︿') == 2
// RuneWidth(0x27) == 0
func RuneWidth(r rune) int {
return runewidth.RuneWidth(r)
}

// Snip returns the given string with a fixed length. For ex.:
// Snip("Ghost", 0, "~") == "Ghost"
// Snip("Ghost", 1, "~") == "~"
Expand Down
30 changes: 30 additions & 0 deletions text/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ func ExampleInsertEveryN() {

func TestInsertEveryN(t *testing.T) {
assert.Equal(t, "Ghost", InsertEveryN("Ghost", '-', 0))
assert.Equal(t, "Gツhツoツsツt", InsertEveryN("Ghost", 'ツ', 1))
assert.Equal(t, "G-h-o-s-t", InsertEveryN("Ghost", '-', 1))
assert.Equal(t, "Gh-os-t", InsertEveryN("Ghost", '-', 2))
assert.Equal(t, "Gho-st", InsertEveryN("Ghost", '-', 3))
assert.Equal(t, "Ghos-t", InsertEveryN("Ghost", '-', 4))
assert.Equal(t, "Ghost", InsertEveryN("Ghost", '-', 5))
assert.Equal(t, "\x1b[33mGhost\x1b[0m", InsertEveryN("\x1b[33mGhost\x1b[0m", '-', 0))
assert.Equal(t, "\x1b[33mGツhツoツsツt\x1b[0m", InsertEveryN("\x1b[33mGhost\x1b[0m", 'ツ', 1))
assert.Equal(t, "\x1b[33mG-h-o-s-t\x1b[0m", InsertEveryN("\x1b[33mGhost\x1b[0m", '-', 1))
assert.Equal(t, "\x1b[33mGh-os-t\x1b[0m", InsertEveryN("\x1b[33mGhost\x1b[0m", '-', 2))
assert.Equal(t, "\x1b[33mGho-st\x1b[0m", InsertEveryN("\x1b[33mGhost\x1b[0m", '-', 3))
Expand All @@ -54,13 +56,15 @@ func ExampleLongestLineLen() {
fmt.Printf("LongestLineLen(\"\"): %d\n", LongestLineLen(""))
fmt.Printf("LongestLineLen(\"\\n\\n\"): %d\n", LongestLineLen("\n\n"))
fmt.Printf("LongestLineLen(\"Ghost\"): %d\n", LongestLineLen("Ghost"))
fmt.Printf("LongestLineLen(\"Ghostツ\"): %d\n", LongestLineLen("Ghostツ"))
fmt.Printf("LongestLineLen(\"Winter\\nIs\\nComing\"): %d\n", LongestLineLen("Winter\nIs\nComing"))
fmt.Printf("LongestLineLen(\"Mother\\nOf\\nDragons\"): %d\n", LongestLineLen("Mother\nOf\nDragons"))
fmt.Printf("LongestLineLen(\"\\x1b[33mMother\\x1b[0m\\nOf\\nDragons\"): %d\n", LongestLineLen("\x1b[33mMother\x1b[0m\nOf\nDragons"))

// Output: LongestLineLen(""): 0
// LongestLineLen("\n\n"): 0
// LongestLineLen("Ghost"): 5
// LongestLineLen("Ghostツ"): 7
// LongestLineLen("Winter\nIs\nComing"): 6
// LongestLineLen("Mother\nOf\nDragons"): 7
// LongestLineLen("\x1b[33mMother\x1b[0m\nOf\nDragons"): 7
Expand All @@ -70,6 +74,7 @@ func TestLongestLineLen(t *testing.T) {
assert.Equal(t, 0, LongestLineLen(""))
assert.Equal(t, 0, LongestLineLen("\n\n"))
assert.Equal(t, 5, LongestLineLen("Ghost"))
assert.Equal(t, 7, LongestLineLen("Ghostツ"))
assert.Equal(t, 6, LongestLineLen("Winter\nIs\nComing"))
assert.Equal(t, 7, LongestLineLen("Mother\nOf\nDragons"))
assert.Equal(t, 7, LongestLineLen("\x1b[33mMother\x1b[0m\nOf\nDragons"))
Expand Down Expand Up @@ -125,22 +130,47 @@ func TestRepeatAndTrim(t *testing.T) {
func ExampleRuneCount() {
fmt.Printf("RuneCount(\"\"): %d\n", RuneCount(""))
fmt.Printf("RuneCount(\"Ghost\"): %d\n", RuneCount("Ghost"))
fmt.Printf("RuneCount(\"Ghostツ\"): %d\n", RuneCount("Ghostツ"))
fmt.Printf("RuneCount(\"\\x1b[33mGhost\\x1b[0m\"): %d\n", RuneCount("\x1b[33mGhost\x1b[0m"))
fmt.Printf("RuneCount(\"\\x1b[33mGhost\\x1b[0\"): %d\n", RuneCount("\x1b[33mGhost\x1b[0"))

// Output: RuneCount(""): 0
// RuneCount("Ghost"): 5
// RuneCount("Ghostツ"): 7
// RuneCount("\x1b[33mGhost\x1b[0m"): 5
// RuneCount("\x1b[33mGhost\x1b[0"): 5
}

func TestRuneCount(t *testing.T) {
assert.Equal(t, 0, RuneCount(""))
assert.Equal(t, 5, RuneCount("Ghost"))
assert.Equal(t, 7, RuneCount("Ghostツ"))
assert.Equal(t, 5, RuneCount("\x1b[33mGhost\x1b[0m"))
assert.Equal(t, 5, RuneCount("\x1b[33mGhost\x1b[0"))
}

func ExampleRuneWidth() {
fmt.Printf("RuneWidth('A'): %d\n", RuneWidth('A'))
fmt.Printf("RuneWidth('ツ'): %d\n", RuneWidth('ツ'))
fmt.Printf("RuneWidth('⊙'): %d\n", RuneWidth('⊙'))
fmt.Printf("RuneWidth('︿'): %d\n", RuneWidth('︿'))
fmt.Printf("RuneWidth(rune(27)): %d\n", RuneWidth(rune(27))) // ANSI escape sequence

// Output: RuneWidth('A'): 1
// RuneWidth('ツ'): 2
// RuneWidth('⊙'): 1
// RuneWidth('︿'): 2
// RuneWidth(rune(27)): 0
}

func TestRuneWidth(t *testing.T) {
assert.Equal(t, 1, RuneWidth('A'))
assert.Equal(t, 2, RuneWidth('ツ'))
assert.Equal(t, 1, RuneWidth('⊙'))
assert.Equal(t, 2, RuneWidth('︿'))
assert.Equal(t, 0, RuneWidth(rune(27))) // ANSI escape sequence
}

func ExampleSnip() {
fmt.Printf("Snip(\"Ghost\", 0, \"~\"): %#v\n", Snip("Ghost", 0, "~"))
fmt.Printf("Snip(\"Ghost\", 1, \"~\"): %#v\n", Snip("Ghost", 1, "~"))
Expand Down
8 changes: 8 additions & 0 deletions vendor/github.com/mattn/go-runewidth/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/mattn/go-runewidth/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions vendor/github.com/mattn/go-runewidth/README.mkd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 71f451d

Please sign in to comment.