Skip to content

Commit

Permalink
deprecate text.RuneCount (fixes #209)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed Jun 20, 2022
1 parent e34eb92 commit e5eeb9b
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 48 deletions.
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

0 comments on commit e5eeb9b

Please sign in to comment.