...
1 package text
2
3 import (
4 "strings"
5 "unicode/utf8"
6
7 "github.com/mattn/go-runewidth"
8 )
9
10
11 const (
12 EscapeReset = EscapeStart + "0" + EscapeStop
13 EscapeStart = "\x1b["
14 EscapeStartRune = rune(27)
15 EscapeStop = "m"
16 EscapeStopRune = 'm'
17 )
18
19
20 var (
21 rwCondition = runewidth.NewCondition()
22 )
23
24
25
26
27
28
29
30 func InsertEveryN(str string, runeToInsert rune, n int) string {
31 if n <= 0 {
32 return str
33 }
34
35 sLen := RuneWidthWithoutEscSequences(str)
36 var out strings.Builder
37 out.Grow(sLen + (sLen / n))
38 outLen, isEscSeq := 0, false
39 for idx, c := range str {
40 if c == EscapeStartRune {
41 isEscSeq = true
42 }
43
44 if !isEscSeq && outLen > 0 && (outLen%n) == 0 && idx != sLen {
45 out.WriteRune(runeToInsert)
46 }
47 out.WriteRune(c)
48 if !isEscSeq {
49 outLen += RuneWidth(c)
50 }
51
52 if isEscSeq && c == EscapeStopRune {
53 isEscSeq = false
54 }
55 }
56 return out.String()
57 }
58
59
60
61
62 func LongestLineLen(str string) int {
63 maxLength, currLength, isEscSeq := 0, 0, false
64 for _, c := range str {
65 if c == EscapeStartRune {
66 isEscSeq = true
67 } else if isEscSeq && c == EscapeStopRune {
68 isEscSeq = false
69 continue
70 }
71
72 if c == '\n' {
73 if currLength > maxLength {
74 maxLength = currLength
75 }
76 currLength = 0
77 } else if !isEscSeq {
78 currLength += RuneWidth(c)
79 }
80 }
81 if currLength > maxLength {
82 maxLength = currLength
83 }
84 return maxLength
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 func OverrideRuneWidthEastAsianWidth(val bool) {
101 rwCondition.EastAsianWidth = val
102 }
103
104
105
106
107
108
109
110
111
112 func Pad(str string, maxLen int, paddingChar rune) string {
113 strLen := RuneWidthWithoutEscSequences(str)
114 if strLen < maxLen {
115 str += strings.Repeat(string(paddingChar), maxLen-strLen)
116 }
117 return str
118 }
119
120
121
122
123
124
125
126
127 func RepeatAndTrim(str string, maxRunes int) string {
128 if str == "" || maxRunes == 0 {
129 return ""
130 } else if maxRunes == utf8.RuneCountInString(str) {
131 return str
132 }
133 repeatedS := strings.Repeat(str, int(maxRunes/utf8.RuneCountInString(str))+1)
134 return Trim(repeatedS, maxRunes)
135 }
136
137
138
139
140
141
142
143
144 func RuneCount(str string) int {
145 return RuneWidthWithoutEscSequences(str)
146 }
147
148
149
150
151
152
153
154
155
156 func RuneWidth(r rune) int {
157 return rwCondition.RuneWidth(r)
158 }
159
160
161
162
163
164
165
166 func RuneWidthWithoutEscSequences(str string) int {
167 count, isEscSeq := 0, false
168 for _, c := range str {
169 if c == EscapeStartRune {
170 isEscSeq = true
171 } else if isEscSeq {
172 if c == EscapeStopRune {
173 isEscSeq = false
174 }
175 } else {
176 count += RuneWidth(c)
177 }
178 }
179 return count
180 }
181
182
183
184
185
186
187
188
189 func Snip(str string, length int, snipIndicator string) string {
190 if length > 0 {
191 lenStr := RuneWidthWithoutEscSequences(str)
192 if lenStr > length {
193 lenStrFinal := length - RuneWidthWithoutEscSequences(snipIndicator)
194 return Trim(str, lenStrFinal) + snipIndicator
195 }
196 }
197 return str
198 }
199
200
201
202
203
204
205
206 func Trim(str string, maxLen int) string {
207 if maxLen <= 0 {
208 return ""
209 }
210
211 var out strings.Builder
212 out.Grow(maxLen)
213
214 outLen, isEscSeq, lastEscSeq := 0, false, strings.Builder{}
215 for _, sChr := range str {
216 out.WriteRune(sChr)
217 if sChr == EscapeStartRune {
218 isEscSeq = true
219 lastEscSeq.Reset()
220 lastEscSeq.WriteRune(sChr)
221 } else if isEscSeq {
222 lastEscSeq.WriteRune(sChr)
223 if sChr == EscapeStopRune {
224 isEscSeq = false
225 }
226 } else {
227 outLen++
228 if outLen == maxLen {
229 break
230 }
231 }
232 }
233 if lastEscSeq.Len() > 0 && lastEscSeq.String() != EscapeReset {
234 out.WriteString(EscapeReset)
235 }
236 return out.String()
237 }
238
View as plain text