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}