diff options
Diffstat (limited to 'util')
-rw-r--r-- | util/colors/colors.go | 1 | ||||
-rw-r--r-- | util/text/text.go | 32 | ||||
-rw-r--r-- | util/text/text_test.go | 82 |
3 files changed, 103 insertions, 12 deletions
diff --git a/util/colors/colors.go b/util/colors/colors.go index c57e9019..f8c6d188 100644 --- a/util/colors/colors.go +++ b/util/colors/colors.go @@ -13,6 +13,7 @@ var ( YellowBg = color.New(color.BgYellow, color.FgBlack).SprintFunc() Green = color.New(color.FgGreen).SprintFunc() GreenBg = color.New(color.BgGreen, color.FgBlack).SprintFunc() + GreyBold = color.New(color.BgBlack, color.Bold).SprintfFunc() Red = color.New(color.FgRed).SprintFunc() Cyan = color.New(color.FgCyan).SprintFunc() CyanBg = color.New(color.BgCyan, color.FgBlack).SprintFunc() diff --git a/util/text/text.go b/util/text/text.go index 10b70b01..c000596c 100644 --- a/util/text/text.go +++ b/util/text/text.go @@ -58,7 +58,7 @@ func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) { } else { // Break a word longer than a line if wordLength > lineWidth { - for wordLength > 0 && len(word) > 0 { + for wordLength > 0 && wordLen(word) > 0 { l := minInt(spaceLeft, wordLength) part, leftover := splitWord(word, l) word = leftover @@ -76,6 +76,10 @@ func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) { nbLine++ spaceLeft = lineWidth - leftPad } + + if wordLength <= 0 { + break + } } } else { // Normal break @@ -84,20 +88,24 @@ func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) { lineBuffer.Reset() lineBuffer.WriteString(word) firstWord = false - spaceLeft = lineWidth - wordLength + spaceLeft = lineWidth - leftPad - wordLength nbLine++ } } } - textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " ")) - lineBuffer.Reset() + if lineBuffer.Len() > 0 { + textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " ")) + lineBuffer.Reset() + } + firstLine = false } return textBuffer.String(), nbLine } +// wordLen return the length of a word, while ignoring the terminal escape sequences func wordLen(word string) int { length := 0 escape := false @@ -119,8 +127,10 @@ func wordLen(word string) int { return length } +// splitWord split a word at the given length, while ignoring the terminal escape sequences func splitWord(word string, length int) (string, string) { - result := "" + runes := []rune(word) + var result []rune added := 0 escape := false @@ -128,12 +138,12 @@ func splitWord(word string, length int) (string, string) { return "", word } - for _, char := range word { - if char == '\x1b' { + for _, r := range runes { + if r == '\x1b' { escape = true } - result += string(char) + result = append(result, r) if !escape { added++ @@ -142,14 +152,14 @@ func splitWord(word string, length int) (string, string) { } } - if char == 'm' { + if r == 'm' { escape = false } } - leftover := word[len(result):] + leftover := runes[len(result):] - return result, leftover + return string(result), string(leftover) } func minInt(a, b int) int { diff --git a/util/text/text_test.go b/util/text/text_test.go index 96234464..cf72431e 100644 --- a/util/text/text_test.go +++ b/util/text/text_test.go @@ -89,12 +89,24 @@ func TestWrap(t *testing.T) { " This\nis a\nlist:\n\n\n *\nfoo\n *\nbar\n\n\n *\nbaz\nBAM\n", 6, }, + // Handle chinese + { + "婞一枳郲逴靲屮蜧曀殳,掫乇峔掮傎溒兀緉冘仜。", + "婞一枳郲逴靲屮蜧曀殳,掫\n乇峔掮傎溒兀緉冘仜。", + 12, + }, + // Handle chinese with colors + { + "婞一枳郲逴\x1b[31m靲屮蜧曀殳,掫乇峔掮傎溒\x1b[0m兀緉冘仜。", + "婞一枳郲逴\x1b[31m靲屮蜧曀殳,掫\n乇峔掮傎溒\x1b[0m兀緉冘仜。", + 12, + }, } for i, tc := range cases { actual, lines := Wrap(tc.Input, tc.Lim) if actual != tc.Output { - t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n\n`%s`\n\nActual Output:\n\n`%s`", + t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n\n`%s`\n\nActual Output:\n`\n%s`", i, tc.Input, tc.Output, actual) } @@ -106,6 +118,47 @@ func TestWrap(t *testing.T) { } } +func TestWrapLeftPadded(t *testing.T) { + cases := []struct { + input, output string + lim, pad int + }{ + { + "The Lorem ipsum text is typically composed of pseudo-Latin words. It is commonly used as placeholder text to examine or demonstrate the visual effects of various graphic design.", + ` The Lorem ipsum text is typically composed of + pseudo-Latin words. It is commonly used as placeholder + text to examine or demonstrate the visual effects of + various graphic design.`, + 59, 4, + }, + // Handle Chinese + { + "婞一枳郲逴靲屮蜧曀殳,掫乇峔掮傎溒兀緉冘仜。郼牪艽螗媷錵朸一詅掜豗怙刉笀丌,楀棶乇矹迡搦囷圣亍昄漚粁仈祂。覂一洳袶揙楱亍滻瘯毌,掗屮柅軡菵腩乜榵毌夯。勼哻怌婇怤灟葠雺奷朾恦扰衪岨坋誁乇芚誙腞。冇笉妺悆浂鱦賌廌灱灱觓坋佫呬耴跣兀枔蓔輈。嵅咍犴膰痭瘰机一靬涽捊矷尒玶乇,煚塈丌岰陊鉖怞戉兀甿跾觓夬侄。棩岧汌橩僁螗玎一逭舴圂衪扐衲兀,嵲媕亍衩衿溽昃夯丌侄蒰扂丱呤。毰侘妅錣廇螉仴一暀淖蚗佶庂咺丌,輀鈁乇彽洢溦洰氶乇构碨洐巿阹。", + ` 婞一枳郲逴靲屮蜧曀殳,掫乇峔掮傎溒兀緉冘仜。郼牪艽螗媷錵朸一詅掜豗怙刉笀丌,楀棶乇矹迡搦囷圣亍昄漚粁仈祂。覂一 + 洳袶揙楱亍滻瘯毌,掗屮柅軡菵腩乜榵毌夯。勼哻怌婇怤灟葠雺奷朾恦扰衪岨坋誁乇芚誙腞。冇笉妺悆浂鱦賌廌灱灱觓坋佫呬 + 耴跣兀枔蓔輈。嵅咍犴膰痭瘰机一靬涽捊矷尒玶乇,煚塈丌岰陊鉖怞戉兀甿跾觓夬侄。棩岧汌橩僁螗玎一逭舴圂衪扐衲兀,嵲 + 媕亍衩衿溽昃夯丌侄蒰扂丱呤。毰侘妅錣廇螉仴一暀淖蚗佶庂咺丌,輀鈁乇彽洢溦洰氶乇构碨洐巿阹。`, + 59, 4, + }, + } + + for i, tc := range cases { + actual, lines := WrapLeftPadded(tc.input, tc.lim, tc.pad) + if actual != tc.output { + t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n`\n%s`\n\nActual Output:\n`\n%s\n%s`", + i, tc.input, tc.output, + "|"+strings.Repeat("-", tc.lim-2)+"|", + actual) + } + + expected := len(strings.Split(tc.output, "\n")) + if expected != lines { + t.Fatalf("Case %d Nb lines mismatch\nExpected:%d\nActual:%d", + i, expected, lines) + } + } +} + func TestWordLen(t *testing.T) { cases := []struct { Input string @@ -126,6 +179,16 @@ func TestWordLen(t *testing.T) { "foo\x1b[31mfoobarHoy\x1b[0mbaaar", 17, }, + // Handle chinese + { + "快檢什麼望對", + 6, + }, + // Handle chinese with colors + { + "快\x1b[31m檢什麼\x1b[0m望對", + 6, + }, } for i, tc := range cases { @@ -179,6 +242,23 @@ func TestSplitWord(t *testing.T) { 0, "", "foo", }, + // Handle chinese + { + "快檢什麼望對", + 2, + "快檢", "什麼望對", + }, + { + "快檢什麼望對", + 3, + "快檢什", "麼望對", + }, + // Handle chinese with colors + { + "快\x1b[31m檢什麼\x1b[0m望對", + 2, + "快\x1b[31m檢", "什麼\x1b[0m望對", + }, } for i, tc := range cases { |