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