1package util
  2
  3import (
  4	"bytes"
  5	"strings"
  6)
  7
  8func WordWrap(text string, lineWidth int) (string, int) {
  9	words := strings.Fields(strings.TrimSpace(text))
 10	if len(words) == 0 {
 11		return "", 1
 12	}
 13	lines := 1
 14	wrapped := words[0]
 15	spaceLeft := lineWidth - len(wrapped)
 16	for _, word := range words[1:] {
 17		if len(word)+1 > spaceLeft {
 18			wrapped += "\n" + word
 19			spaceLeft = lineWidth - len(word)
 20			lines++
 21		} else {
 22			wrapped += " " + word
 23			spaceLeft -= 1 + len(word)
 24		}
 25	}
 26
 27	return wrapped, lines
 28}
 29
 30// Wrap a text for an exact line size
 31// Handle properly terminal color escape code
 32func TextWrap(text string, lineWidth int) (string, int) {
 33	return TextWrapPadded(text, lineWidth, 0)
 34}
 35
 36// Wrap a text for an exact line size with a left padding
 37// Handle properly terminal color escape code
 38func TextWrapPadded(text string, lineWidth int, leftPad int) (string, int) {
 39	var textBuffer bytes.Buffer
 40	var lineBuffer bytes.Buffer
 41	nbLine := 1
 42	firstLine := true
 43	pad := strings.Repeat(" ", leftPad)
 44
 45	// tabs are formatted as 4 spaces
 46	text = strings.Replace(text, "\t", "    ", 4)
 47
 48	for _, line := range strings.Split(text, "\n") {
 49		spaceLeft := lineWidth - leftPad
 50
 51		if !firstLine {
 52			textBuffer.WriteString("\n")
 53			nbLine++
 54		}
 55
 56		firstWord := true
 57
 58		for _, word := range strings.Split(line, " ") {
 59			wordLength := wordLen(word)
 60
 61			if !firstWord {
 62				lineBuffer.WriteString(" ")
 63				spaceLeft -= 1
 64
 65				if spaceLeft <= 0 {
 66					textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
 67					textBuffer.WriteString("\n")
 68					lineBuffer.Reset()
 69					spaceLeft = lineWidth - leftPad
 70					nbLine++
 71					firstLine = false
 72				}
 73			}
 74
 75			// Word fit in the current line
 76			if spaceLeft >= wordLength {
 77				lineBuffer.WriteString(word)
 78				spaceLeft -= wordLength
 79				firstWord = false
 80			} else {
 81				// Break a word longer than a line
 82				if wordLength > lineWidth {
 83					for wordLength > 0 && len(word) > 0 {
 84						l := minInt(spaceLeft, wordLength)
 85						part, leftover := splitWord(word, l)
 86						word = leftover
 87						wordLength = wordLen(word)
 88
 89						lineBuffer.WriteString(part)
 90						textBuffer.WriteString(pad)
 91						textBuffer.Write(lineBuffer.Bytes())
 92						lineBuffer.Reset()
 93
 94						spaceLeft -= l
 95
 96						if spaceLeft <= 0 {
 97							textBuffer.WriteString("\n")
 98							nbLine++
 99							spaceLeft = lineWidth - leftPad
100						}
101					}
102				} else {
103					// Normal break
104					textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
105					textBuffer.WriteString("\n")
106					lineBuffer.Reset()
107					lineBuffer.WriteString(word)
108					firstWord = false
109					spaceLeft = lineWidth - wordLength
110					nbLine++
111				}
112			}
113		}
114
115		textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
116		lineBuffer.Reset()
117		firstLine = false
118	}
119
120	return textBuffer.String(), nbLine
121}
122
123func wordLen(word string) int {
124	length := 0
125	escape := false
126
127	for _, char := range word {
128		if char == '\x1b' {
129			escape = true
130		}
131
132		if !escape {
133			length++
134		}
135
136		if char == 'm' {
137			escape = false
138		}
139	}
140
141	return length
142}
143
144func splitWord(word string, length int) (string, string) {
145	result := ""
146	added := 0
147	escape := false
148
149	if length == 0 {
150		return "", word
151	}
152
153	for _, char := range word {
154		if char == '\x1b' {
155			escape = true
156		}
157
158		result += string(char)
159
160		if !escape {
161			added++
162			if added == length {
163				break
164			}
165		}
166
167		if char == 'm' {
168			escape = false
169		}
170	}
171
172	leftover := word[len(result):]
173
174	return result, leftover
175}
176
177func minInt(a, b int) int {
178	if a > b {
179		return b
180	}
181	return a
182}