Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes for issues #209 and #210 #211

Merged
merged 2 commits into from Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions go.mod
Expand Up @@ -3,9 +3,8 @@ module github.com/jedib0t/go-pretty/v6
go 1.16

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-runewidth v0.0.13
github.com/pkg/profile v1.6.0
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.4
golang.org/x/sys v0.0.0-20190412213103-97732733099d
)
9 changes: 6 additions & 3 deletions go.sum
Expand Up @@ -10,11 +10,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 4 additions & 4 deletions progress/indicator.go
Expand Up @@ -94,7 +94,7 @@ func indeterminateIndicatorMovingBackAndForth(indicator string) IndeterminateInd

if currentPosition == 0 {
direction = 1
} else if currentPosition+text.RuneCount(indicator) == maxLen {
} else if currentPosition+text.RuneWidthWithoutEscSequences(indicator) == maxLen {
direction = -1
}
nextPosition += direction
Expand All @@ -113,7 +113,7 @@ func indeterminateIndicatorMovingLeftToRight(indicator string) IndeterminateIndi
currentPosition := nextPosition

nextPosition++
if nextPosition+text.RuneCount(indicator) > maxLen {
if nextPosition+text.RuneWidthWithoutEscSequences(indicator) > maxLen {
nextPosition = 0
}

Expand All @@ -129,7 +129,7 @@ func indeterminateIndicatorMovingRightToLeft(indicator string) IndeterminateIndi

return func(maxLen int) IndeterminateIndicator {
if nextPosition == -1 {
nextPosition = maxLen - text.RuneCount(indicator)
nextPosition = maxLen - text.RuneWidthWithoutEscSequences(indicator)
}
currentPosition := nextPosition
nextPosition--
Expand Down Expand Up @@ -165,7 +165,7 @@ func indeterminateIndicatorPacMan() IndeterminateIndicatorGenerator {
if currentPosition == 0 {
direction = 1
indicator = pacManMovingRight
} else if currentPosition+text.RuneCount(indicator) == maxLen {
} else if currentPosition+text.RuneWidthWithoutEscSequences(indicator) == maxLen {
direction = -1
indicator = pacManMovingLeft
}
Expand Down
4 changes: 2 additions & 2 deletions progress/progress.go
Expand Up @@ -282,10 +282,10 @@ func (p *Progress) initForRender() {
utf8.RuneCountInString(p.style.Chars.BoxLeft) -
utf8.RuneCountInString(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.messageWidth +
text.RuneCount(p.style.Options.Separator) +
text.RuneWidthWithoutEscSequences(p.style.Options.Separator) +
p.lengthProgress + 1
if p.style.Visibility.Percentage {
p.lengthProgressOverall += text.RuneCount(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
}

// if not output write has been set, output to STDOUT
Expand Down
8 changes: 4 additions & 4 deletions progress/render.go
Expand Up @@ -124,7 +124,7 @@ func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLe
} else if pFinishedDotsFraction == 0 {
pInProgress = ""
}
pFinishedStrLen := text.RuneCount(pFinished + pInProgress)
pFinishedStrLen := text.RuneWidthWithoutEscSequences(pFinished + pInProgress)
if pFinishedStrLen < maxLen {
pUnfinished = strings.Repeat(p.style.Chars.Unfinished, maxLen-pFinishedStrLen)
}
Expand All @@ -144,8 +144,8 @@ func (p *Progress) generateTrackerStrIndeterminate(maxLen int) string {
pUnfinished += strings.Repeat(p.style.Chars.Unfinished, indicator.Position)
}
pUnfinished += indicator.Text
if text.RuneCount(pUnfinished) < maxLen {
pUnfinished += strings.Repeat(p.style.Chars.Unfinished, maxLen-text.RuneCount(pUnfinished))
if text.RuneWidthWithoutEscSequences(pUnfinished) < maxLen {
pUnfinished += strings.Repeat(p.style.Chars.Unfinished, maxLen-text.RuneWidthWithoutEscSequences(pUnfinished))
}

return p.style.Colors.Tracker.Sprintf("%s%s%s",
Expand All @@ -172,7 +172,7 @@ func (p *Progress) renderTracker(out *strings.Builder, t *Tracker, hint renderHi
message = strings.Replace(message, "\r", "", -1)
}
if p.messageWidth > 0 {
messageLen := text.RuneCount(message)
messageLen := text.RuneWidthWithoutEscSequences(message)
if messageLen < p.messageWidth {
message = text.Pad(message, p.messageWidth, ' ')
} else {
Expand Down
8 changes: 4 additions & 4 deletions table/render.go
Expand Up @@ -221,7 +221,7 @@ func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) {

func (t *Table) renderLineMergeOutputs(out *strings.Builder, outLine *strings.Builder) {
outLineStr := outLine.String()
if text.RuneCount(outLineStr) > t.allowedRowLength {
if text.RuneWidthWithoutEscSequences(outLineStr) > t.allowedRowLength {
trimLength := t.allowedRowLength - utf8.RuneCountInString(t.style.Box.UnfinishedRow)
if trimLength > 0 {
out.WriteString(text.Trim(outLineStr, trimLength))
Expand Down Expand Up @@ -358,15 +358,15 @@ func (t *Table) renderTitle(out *strings.Builder) {
rowLength = t.allowedRowLength
}
if t.style.Options.DrawBorder {
lenBorder := rowLength - text.RuneCount(t.style.Box.TopLeft+t.style.Box.TopRight)
lenBorder := rowLength - text.RuneWidthWithoutEscSequences(t.style.Box.TopLeft+t.style.Box.TopRight)
out.WriteString(t.style.Box.TopLeft)
out.WriteString(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, lenBorder))
out.WriteString(t.style.Box.TopRight)
}

lenText := rowLength - text.RuneCount(t.style.Box.PaddingLeft+t.style.Box.PaddingRight)
lenText := rowLength - text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft+t.style.Box.PaddingRight)
if t.style.Options.DrawBorder {
lenText -= text.RuneCount(t.style.Box.Left + t.style.Box.Right)
lenText -= text.RuneWidthWithoutEscSequences(t.style.Box.Left + t.style.Box.Right)
}
titleText := text.WrapText(t.title, lenText)
for _, titleLine := range strings.Split(titleText, "\n") {
Expand Down
12 changes: 6 additions & 6 deletions table/style.go
Expand Up @@ -358,7 +358,7 @@ var (
BottomLeft: "+",
BottomRight: "+",
BottomSeparator: "+",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("+")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("+")),
Left: "|",
LeftSeparator: "+",
MiddleHorizontal: "-",
Expand Down Expand Up @@ -389,7 +389,7 @@ var (
BottomLeft: "┗",
BottomRight: "┛",
BottomSeparator: "┻",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("╋")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("╋")),
Left: "┃",
LeftSeparator: "┣",
MiddleHorizontal: "━",
Expand Down Expand Up @@ -420,7 +420,7 @@ var (
BottomLeft: "╚",
BottomRight: "╝",
BottomSeparator: "╩",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("╬")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("╬")),
Left: "║",
LeftSeparator: "╠",
MiddleHorizontal: "═",
Expand Down Expand Up @@ -451,7 +451,7 @@ var (
BottomLeft: "└",
BottomRight: "┘",
BottomSeparator: "┴",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("┼")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("┼")),
Left: "│",
LeftSeparator: "├",
MiddleHorizontal: "─",
Expand Down Expand Up @@ -482,7 +482,7 @@ var (
BottomLeft: "╰",
BottomRight: "╯",
BottomSeparator: "┴",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("┼")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("┼")),
Left: "│",
LeftSeparator: "├",
MiddleHorizontal: "─",
Expand Down Expand Up @@ -513,7 +513,7 @@ var (
BottomLeft: "\\",
BottomRight: "/",
BottomSeparator: "v",
EmptySeparator: text.RepeatAndTrim(" ", text.RuneCount("+")),
EmptySeparator: text.RepeatAndTrim(" ", text.RuneWidthWithoutEscSequences("+")),
Left: "[",
LeftSeparator: "{",
MiddleHorizontal: "--",
Expand Down
16 changes: 8 additions & 8 deletions table/table.go
Expand Up @@ -550,9 +550,9 @@ func (t *Table) getFormat(hint renderHint) text.Format {

func (t *Table) getMaxColumnLengthForMerging(colIdx int) int {
maxColumnLength := t.maxColumnLengths[colIdx]
maxColumnLength += text.RuneCount(t.style.Box.PaddingRight + t.style.Box.PaddingLeft)
maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight + t.style.Box.PaddingLeft)
if t.style.Options.SeparateColumns {
maxColumnLength += text.RuneCount(t.style.Box.EmptySeparator)
maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.EmptySeparator)
}
return maxColumnLength
}
Expand Down Expand Up @@ -781,24 +781,24 @@ func (t *Table) initForRenderRowsStringify(rows []Row, hint renderHint) []rowStr
func (t *Table) initForRenderRowSeparator() {
t.maxRowLength = 0
if t.autoIndex {
t.maxRowLength += text.RuneCount(t.style.Box.PaddingLeft)
t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft)
t.maxRowLength += len(fmt.Sprint(len(t.rows)))
t.maxRowLength += text.RuneCount(t.style.Box.PaddingRight)
t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight)
if t.style.Options.SeparateColumns {
t.maxRowLength += text.RuneCount(t.style.Box.MiddleSeparator)
t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator)
}
}
if t.style.Options.SeparateColumns {
t.maxRowLength += text.RuneCount(t.style.Box.MiddleSeparator) * (t.numColumns - 1)
t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) * (t.numColumns - 1)
}
t.rowSeparator = make(rowStr, t.numColumns)
for colIdx, maxColumnLength := range t.maxColumnLengths {
maxColumnLength += text.RuneCount(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
t.maxRowLength += maxColumnLength
t.rowSeparator[colIdx] = text.RepeatAndTrim(t.style.Box.MiddleHorizontal, maxColumnLength)
}
if t.style.Options.DrawBorder {
t.maxRowLength += text.RuneCount(t.style.Box.Left + t.style.Box.Right)
t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.Left + t.style.Box.Right)
}
}

Expand Down
2 changes: 1 addition & 1 deletion text/align.go
Expand Up @@ -28,7 +28,7 @@ const (
func (a Align) Apply(text string, maxLength int) string {
text = a.trimString(text)
sLen := utf8.RuneCountInString(text)
sLenWoE := RuneCount(text)
sLenWoE := RuneWidthWithoutEscSequences(text)
numEscChars := sLen - sLenWoE

// now, align the text
Expand Down
2 changes: 1 addition & 1 deletion text/ansi.go
Expand Up @@ -37,7 +37,7 @@ func Escape(str string, escapeSeq string) string {
// StripEscape("\x1b[91mNymeria \x1b[94mGhost\x1b[0m\x1b[91m Lady\x1b[0m") == "Nymeria Ghost Lady"
func StripEscape(str string) string {
var out strings.Builder
out.Grow(RuneCount(str))
out.Grow(RuneWidthWithoutEscSequences(str))

isEscSeq := false
for _, sChr := range str {
Expand Down
43 changes: 27 additions & 16 deletions text/string.go
Expand Up @@ -27,7 +27,7 @@ func InsertEveryN(str string, runeToInsert rune, n int) string {
return str
}

sLen := RuneCount(str)
sLen := RuneWidthWithoutEscSequences(str)
var out strings.Builder
out.Grow(sLen + (sLen / n))
outLen, isEscSeq := 0, false
Expand Down Expand Up @@ -88,7 +88,7 @@ func LongestLineLen(str string) int {
// Pad("Ghost", 7, ' ') == "Ghost "
// Pad("Ghost", 10, '.') == "Ghost....."
func Pad(str string, maxLen int, paddingChar rune) string {
strLen := RuneCount(str)
strLen := RuneWidthWithoutEscSequences(str)
if strLen < maxLen {
str += strings.Repeat(string(paddingChar), maxLen-strLen)
}
Expand Down Expand Up @@ -118,7 +118,30 @@ func RepeatAndTrim(str string, maxRunes int) string {
// RuneCount("Ghost") == 5
// RuneCount("\x1b[33mGhost\x1b[0m") == 5
// RuneCount("\x1b[33mGhost\x1b[0") == 5
// Deprecated: in favor of RuneWidthWithoutEscSequences
func RuneCount(str string) int {
return RuneWidthWithoutEscSequences(str)
}

// RuneWidth returns the mostly accurate character-width of the rune. This is
// not 100% accurate as the character width is usually dependent 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)
}

// RuneWidthWithoutEscSequences is similar to RuneWidth, except for the fact
// that it ignores escape sequences while counting. For ex.:
// RuneWidthWithoutEscSequences("") == 0
// RuneWidthWithoutEscSequences("Ghost") == 5
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0m") == 5
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0") == 5
func RuneWidthWithoutEscSequences(str string) int {
count, isEscSeq := 0, false
for _, c := range str {
if c == EscapeStartRune {
Expand All @@ -134,18 +157,6 @@ func RuneCount(str string) int {
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 All @@ -155,9 +166,9 @@ func RuneWidth(r rune) int {
// Snip("\x1b[33mGhost\x1b[0m", 7, "~") == "\x1b[33mGhost\x1b[0m "
func Snip(str string, length int, snipIndicator string) string {
if length > 0 {
lenStr := RuneCount(str)
lenStr := RuneWidthWithoutEscSequences(str)
if lenStr > length {
lenStrFinal := length - RuneCount(snipIndicator)
lenStrFinal := length - RuneWidthWithoutEscSequences(snipIndicator)
return Trim(str, lenStrFinal) + snipIndicator
}
}
Expand Down
22 changes: 22 additions & 0 deletions text/string_test.go
Expand Up @@ -174,6 +174,28 @@ func TestRuneWidth(t *testing.T) {
assert.Equal(t, 0, RuneWidth(rune(27))) // ANSI escape sequence
}

func ExampleRuneWidthWithoutEscSequences() {
fmt.Printf("RuneWidthWithoutEscSequences(\"\"): %d\n", RuneWidthWithoutEscSequences(""))
fmt.Printf("RuneWidthWithoutEscSequences(\"Ghost\"): %d\n", RuneWidthWithoutEscSequences("Ghost"))
fmt.Printf("RuneWidthWithoutEscSequences(\"Ghostツ\"): %d\n", RuneWidthWithoutEscSequences("Ghostツ"))
fmt.Printf("RuneWidthWithoutEscSequences(\"\\x1b[33mGhost\\x1b[0m\"): %d\n", RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0m"))
fmt.Printf("RuneWidthWithoutEscSequences(\"\\x1b[33mGhost\\x1b[0\"): %d\n", RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0"))

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

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

func ExampleSnip() {
fmt.Printf("Snip(\"Ghost\", 0, \"~\"): %#v\n", Snip("Ghost", 0, "~"))
fmt.Printf("Snip(\"Ghost\", 1, \"~\"): %#v\n", Snip("Ghost", 1, "~"))
Expand Down
4 changes: 2 additions & 2 deletions text/wrap.go
Expand Up @@ -201,7 +201,7 @@ func wrapHard(paragraph string, wrapLen int, out *strings.Builder) {
lineLen++
}

wordLen := RuneCount(word)
wordLen := RuneWidthWithoutEscSequences(word)
if lineLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(word)
lineLen += wordLen
Expand All @@ -227,7 +227,7 @@ func wrapSoft(paragraph string, wrapLen int, out *strings.Builder) {
}

spacing, spacingLen := wrapSoftSpacing(lineLen)
wordLen := RuneCount(word)
wordLen := RuneWidthWithoutEscSequences(word)
if lineLen+spacingLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(spacing)
out.WriteString(word)
Expand Down