2018-09-11 23:04:16 +03:00
|
|
|
package text
|
2018-08-03 18:29:11 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2018-08-11 23:27:45 +03:00
|
|
|
// Wrap a text for an exact line size
|
|
|
|
// Handle properly terminal color escape code
|
2018-09-11 23:04:16 +03:00
|
|
|
func Wrap(text string, lineWidth int) (string, int) {
|
|
|
|
return WrapLeftPadded(text, lineWidth, 0)
|
2018-08-09 15:35:55 +03:00
|
|
|
}
|
|
|
|
|
2018-08-11 23:27:45 +03:00
|
|
|
// Wrap a text for an exact line size with a left padding
|
|
|
|
// Handle properly terminal color escape code
|
2018-09-11 23:04:16 +03:00
|
|
|
func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) {
|
2018-08-03 18:29:11 +03:00
|
|
|
var textBuffer bytes.Buffer
|
|
|
|
var lineBuffer bytes.Buffer
|
|
|
|
nbLine := 1
|
|
|
|
firstLine := true
|
2018-08-09 15:35:55 +03:00
|
|
|
pad := strings.Repeat(" ", leftPad)
|
2018-08-03 18:29:11 +03:00
|
|
|
|
|
|
|
// tabs are formatted as 4 spaces
|
|
|
|
text = strings.Replace(text, "\t", " ", 4)
|
|
|
|
|
|
|
|
for _, line := range strings.Split(text, "\n") {
|
2018-08-09 15:35:55 +03:00
|
|
|
spaceLeft := lineWidth - leftPad
|
2018-08-03 18:29:11 +03:00
|
|
|
|
|
|
|
if !firstLine {
|
|
|
|
textBuffer.WriteString("\n")
|
|
|
|
nbLine++
|
|
|
|
}
|
|
|
|
|
|
|
|
firstWord := true
|
|
|
|
|
|
|
|
for _, word := range strings.Split(line, " ") {
|
2018-08-11 23:27:45 +03:00
|
|
|
wordLength := wordLen(word)
|
|
|
|
|
|
|
|
if !firstWord {
|
|
|
|
lineBuffer.WriteString(" ")
|
|
|
|
spaceLeft -= 1
|
2018-08-09 15:06:55 +03:00
|
|
|
|
2018-08-11 23:27:45 +03:00
|
|
|
if spaceLeft <= 0 {
|
|
|
|
textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
|
|
|
|
textBuffer.WriteString("\n")
|
|
|
|
lineBuffer.Reset()
|
|
|
|
spaceLeft = lineWidth - leftPad
|
|
|
|
nbLine++
|
|
|
|
firstLine = false
|
2018-08-03 18:29:11 +03:00
|
|
|
}
|
2018-08-11 23:27:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Word fit in the current line
|
|
|
|
if spaceLeft >= wordLength {
|
|
|
|
lineBuffer.WriteString(word)
|
|
|
|
spaceLeft -= wordLength
|
2018-08-03 18:29:11 +03:00
|
|
|
firstWord = false
|
|
|
|
} else {
|
2018-08-11 23:27:45 +03:00
|
|
|
// Break a word longer than a line
|
|
|
|
if wordLength > lineWidth {
|
|
|
|
for wordLength > 0 && len(word) > 0 {
|
|
|
|
l := minInt(spaceLeft, wordLength)
|
|
|
|
part, leftover := splitWord(word, l)
|
|
|
|
word = leftover
|
|
|
|
wordLength = wordLen(word)
|
2018-08-03 18:29:11 +03:00
|
|
|
|
|
|
|
lineBuffer.WriteString(part)
|
2018-08-09 15:35:55 +03:00
|
|
|
textBuffer.WriteString(pad)
|
2018-08-03 18:29:11 +03:00
|
|
|
textBuffer.Write(lineBuffer.Bytes())
|
|
|
|
lineBuffer.Reset()
|
|
|
|
|
2018-08-11 23:27:45 +03:00
|
|
|
spaceLeft -= l
|
|
|
|
|
|
|
|
if spaceLeft <= 0 {
|
2018-08-03 18:29:11 +03:00
|
|
|
textBuffer.WriteString("\n")
|
|
|
|
nbLine++
|
2018-08-11 23:27:45 +03:00
|
|
|
spaceLeft = lineWidth - leftPad
|
2018-08-03 18:29:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-08-11 23:27:45 +03:00
|
|
|
// Normal break
|
2018-08-09 15:35:55 +03:00
|
|
|
textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
|
2018-08-03 18:29:11 +03:00
|
|
|
textBuffer.WriteString("\n")
|
|
|
|
lineBuffer.Reset()
|
2018-08-11 23:27:45 +03:00
|
|
|
lineBuffer.WriteString(word)
|
2018-08-03 18:29:11 +03:00
|
|
|
firstWord = false
|
2018-08-11 23:27:45 +03:00
|
|
|
spaceLeft = lineWidth - wordLength
|
2018-08-03 18:29:11 +03:00
|
|
|
nbLine++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-11 23:27:45 +03:00
|
|
|
|
2018-08-09 15:35:55 +03:00
|
|
|
textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
|
2018-08-03 18:29:11 +03:00
|
|
|
lineBuffer.Reset()
|
|
|
|
firstLine = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return textBuffer.String(), nbLine
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
// wordLen return the length of a word, while ignoring the terminal escape sequences
|
2018-08-11 23:27:45 +03:00
|
|
|
func wordLen(word string) int {
|
|
|
|
length := 0
|
|
|
|
escape := false
|
|
|
|
|
|
|
|
for _, char := range word {
|
|
|
|
if char == '\x1b' {
|
|
|
|
escape = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !escape {
|
|
|
|
length++
|
|
|
|
}
|
|
|
|
|
|
|
|
if char == 'm' {
|
|
|
|
escape = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return length
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
// splitWord split a word at the given length, while ignoring the terminal escape sequences
|
2018-08-11 23:27:45 +03:00
|
|
|
func splitWord(word string, length int) (string, string) {
|
2018-12-23 21:00:51 +03:00
|
|
|
runes := []rune(word)
|
|
|
|
var result []rune
|
2018-08-11 23:27:45 +03:00
|
|
|
added := 0
|
|
|
|
escape := false
|
|
|
|
|
|
|
|
if length == 0 {
|
|
|
|
return "", word
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
for _, r := range runes {
|
|
|
|
if r == '\x1b' {
|
2018-08-11 23:27:45 +03:00
|
|
|
escape = true
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
result = append(result, r)
|
2018-08-11 23:27:45 +03:00
|
|
|
|
|
|
|
if !escape {
|
|
|
|
added++
|
|
|
|
if added == length {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
if r == 'm' {
|
2018-08-11 23:27:45 +03:00
|
|
|
escape = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
leftover := runes[len(result):]
|
2018-08-11 23:27:45 +03:00
|
|
|
|
2018-12-23 21:00:51 +03:00
|
|
|
return string(result), string(leftover)
|
2018-08-11 23:27:45 +03:00
|
|
|
}
|
|
|
|
|
2018-08-03 18:29:11 +03:00
|
|
|
func minInt(a, b int) int {
|
|
|
|
if a > b {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|