1package text
2
3import (
4 "fmt"
5 "strconv"
6 "strings"
7)
8
9const Escape = '\x1b'
10
11type EscapeState struct {
12 Bold bool
13 Dim bool
14 Italic bool
15 Underlined bool
16 Blink bool
17 Reverse bool
18 Hidden bool
19 CrossedOut bool
20
21 FgColor Color
22 BgColor Color
23}
24
25type Color interface {
26 Codes() []string
27}
28
29func (es *EscapeState) Witness(s string) {
30 inEscape := false
31 var start int
32
33 runes := []rune(s)
34
35 for i, r := range runes {
36 if r == Escape {
37 inEscape = true
38 start = i
39 continue
40 }
41 if inEscape {
42 if r == 'm' {
43 inEscape = false
44 es.witnessCode(string(runes[start+1 : i]))
45 }
46 continue
47 }
48 }
49}
50
51func (es *EscapeState) witnessCode(s string) {
52 if s == "" {
53 return
54 }
55 if s == "[" {
56 es.reset()
57 return
58 }
59 if len(s) < 2 {
60 return
61 }
62 if s[0] != '[' {
63 return
64 }
65
66 s = s[1:]
67 split := strings.Split(s, ";")
68
69 dequeue := func() {
70 split = split[1:]
71 }
72
73 color := func(ground int) Color {
74 if len(split) < 1 {
75 // the whole sequence is broken, ignoring the rest
76 return nil
77 }
78
79 subCode := split[0]
80 dequeue()
81
82 switch subCode {
83 case "2":
84 if len(split) < 3 {
85 return nil
86 }
87 r, err := strconv.Atoi(split[0])
88 dequeue()
89 if err != nil {
90 return nil
91 }
92 g, err := strconv.Atoi(split[0])
93 dequeue()
94 if err != nil {
95 return nil
96 }
97 b, err := strconv.Atoi(split[0])
98 dequeue()
99 if err != nil {
100 return nil
101 }
102 return &ColorRGB{ground: ground, R: r, G: g, B: b}
103
104 case "5":
105 if len(split) < 1 {
106 return nil
107 }
108 index, err := strconv.Atoi(split[0])
109 dequeue()
110 if err != nil {
111 return nil
112 }
113 return &Color256{ground: ground, Index: index}
114
115 }
116 return nil
117 }
118
119 for len(split) > 0 {
120 code, err := strconv.Atoi(split[0])
121 if err != nil {
122 return
123 }
124 dequeue()
125
126 switch {
127 case code == 0:
128 es.reset()
129
130 case code == 1:
131 es.Bold = true
132 case code == 2:
133 es.Dim = true
134 case code == 3:
135 es.Italic = true
136 case code == 4:
137 es.Underlined = true
138 case code == 5:
139 es.Blink = true
140 // case code == 6:
141 case code == 7:
142 es.Reverse = true
143 case code == 8:
144 es.Hidden = true
145 case code == 9:
146 es.CrossedOut = true
147
148 case code == 21:
149 es.Bold = false
150 case code == 22:
151 es.Dim = false
152 case code == 23:
153 es.Italic = false
154 case code == 24:
155 es.Underlined = false
156 case code == 25:
157 es.Blink = false
158 // case code == 26:
159 case code == 27:
160 es.Reverse = false
161 case code == 28:
162 es.Hidden = false
163 case code == 29:
164 es.CrossedOut = false
165
166 case (code >= 30 && code <= 37) || code == 39 || (code >= 90 && code <= 97):
167 es.FgColor = ColorIndex(code)
168
169 case (code >= 40 && code <= 47) || code == 49 || (code >= 100 && code <= 107):
170 es.BgColor = ColorIndex(code)
171
172 case code == 38:
173 es.FgColor = color(code)
174 if es.FgColor == nil {
175 return
176 }
177
178 case code == 48:
179 es.BgColor = color(code)
180 if es.BgColor == nil {
181 return
182 }
183 }
184 }
185}
186
187func (es *EscapeState) reset() {
188 *es = EscapeState{}
189}
190
191func (es *EscapeState) String() string {
192 var codes []string
193
194 if es.Bold {
195 codes = append(codes, strconv.Itoa(1))
196 }
197 if es.Dim {
198 codes = append(codes, strconv.Itoa(2))
199 }
200 if es.Italic {
201 codes = append(codes, strconv.Itoa(3))
202 }
203 if es.Underlined {
204 codes = append(codes, strconv.Itoa(4))
205 }
206 if es.Blink {
207 codes = append(codes, strconv.Itoa(5))
208 }
209 if es.Reverse {
210 codes = append(codes, strconv.Itoa(7))
211 }
212 if es.Hidden {
213 codes = append(codes, strconv.Itoa(8))
214 }
215 if es.CrossedOut {
216 codes = append(codes, strconv.Itoa(9))
217 }
218
219 if es.FgColor != nil {
220 codes = append(codes, es.FgColor.Codes()...)
221 }
222 if es.BgColor != nil {
223 codes = append(codes, es.BgColor.Codes()...)
224 }
225
226 if len(codes) == 0 {
227 return "\x1b[0m"
228 }
229
230 return fmt.Sprintf("\x1b[%sm", strings.Join(codes, ";"))
231}
232
233type ColorIndex int
234
235func (cInd ColorIndex) Codes() []string {
236 return []string{strconv.Itoa(int(cInd))}
237}
238
239type Color256 struct {
240 ground int
241 Index int
242}
243
244func (c256 Color256) Codes() []string {
245 return []string{
246 strconv.Itoa(c256.ground),
247 "5",
248 strconv.Itoa(c256.Index),
249 }
250}
251
252type ColorRGB struct {
253 ground int
254 R, G, B int
255}
256
257func (cRGB ColorRGB) Codes() []string {
258 return []string{
259 strconv.Itoa(cRGB.ground),
260 "2",
261 strconv.Itoa(cRGB.R),
262 strconv.Itoa(cRGB.G),
263 strconv.Itoa(cRGB.B),
264 }
265}