1 package cview
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "strconv"
8 "strings"
9 )
10
11
12 const (
13 ansiText = iota
14 ansiEscape
15 ansiSubstring
16 ansiControlSequence
17 )
18
19
20
21 type ansi struct {
22 io.Writer
23
24
25 buffer *bytes.Buffer
26 csiParameter, csiIntermediate *bytes.Buffer
27 attributes string
28
29
30 state int
31 }
32
33
34
35
36
37 func ANSIWriter(writer io.Writer) io.Writer {
38 return &ansi{
39 Writer: writer,
40 buffer: new(bytes.Buffer),
41 csiParameter: new(bytes.Buffer),
42 csiIntermediate: new(bytes.Buffer),
43 state: ansiText,
44 }
45 }
46
47
48
49 func (a *ansi) Write(text []byte) (int, error) {
50 defer func() {
51 a.buffer.Reset()
52 }()
53
54 for _, r := range string(text) {
55 switch a.state {
56
57
58 case ansiEscape:
59 switch r {
60 case '[':
61 a.csiParameter.Reset()
62 a.csiIntermediate.Reset()
63 a.state = ansiControlSequence
64 case 'c':
65 fmt.Fprint(a.buffer, "[-:-:-]")
66 a.state = ansiText
67 case 'P', ']', 'X', '^', '_':
68 a.state = ansiSubstring
69 default:
70 a.state = ansiText
71 }
72
73
74 case ansiControlSequence:
75 switch {
76 case r >= 0x30 && r <= 0x3f:
77 if _, err := a.csiParameter.WriteRune(r); err != nil {
78 return 0, err
79 }
80 case r >= 0x20 && r <= 0x2f:
81 if _, err := a.csiIntermediate.WriteRune(r); err != nil {
82 return 0, err
83 }
84 case r >= 0x40 && r <= 0x7e:
85 switch r {
86 case 'E':
87 count, _ := strconv.Atoi(a.csiParameter.String())
88 if count == 0 {
89 count = 1
90 }
91 fmt.Fprint(a.buffer, strings.Repeat("\n", count))
92 case 'm':
93 var background, foreground string
94 params := a.csiParameter.String()
95 fields := strings.Split(params, ";")
96 if len(params) == 0 || len(fields) == 1 && fields[0] == "0" {
97
98 a.attributes = ""
99 if _, err := a.buffer.WriteString("[-:-:-]"); err != nil {
100 return 0, err
101 }
102 break
103 }
104 lookupColor := func(colorNumber int) string {
105 if colorNumber < 0 || colorNumber > 15 {
106 return "black"
107 }
108 return []string{
109 "black",
110 "maroon",
111 "green",
112 "olive",
113 "navy",
114 "purple",
115 "teal",
116 "silver",
117 "gray",
118 "red",
119 "lime",
120 "yellow",
121 "blue",
122 "fuchsia",
123 "aqua",
124 "white",
125 }[colorNumber]
126 }
127 FieldLoop:
128 for index, field := range fields {
129 switch field {
130 case "1", "01":
131 if strings.IndexRune(a.attributes, 'b') < 0 {
132 a.attributes += "b"
133 }
134 case "2", "02":
135 if strings.IndexRune(a.attributes, 'd') < 0 {
136 a.attributes += "d"
137 }
138 case "3", "03":
139 if strings.IndexRune(a.attributes, 'i') < 0 {
140 a.attributes += "i"
141 }
142 case "4", "04":
143 if strings.IndexRune(a.attributes, 'u') < 0 {
144 a.attributes += "u"
145 }
146 case "5", "05":
147 if strings.IndexRune(a.attributes, 'l') < 0 {
148 a.attributes += "l"
149 }
150 case "7", "07":
151 if strings.IndexRune(a.attributes, 'r') < 0 {
152 a.attributes += "r"
153 }
154 case "9", "09":
155 if strings.IndexRune(a.attributes, 's') < 0 {
156 a.attributes += "s"
157 }
158 case "22":
159 if i := strings.IndexRune(a.attributes, 'b'); i >= 0 {
160 a.attributes = a.attributes[:i] + a.attributes[i+1:]
161 }
162 if i := strings.IndexRune(a.attributes, 'd'); i >= 0 {
163 a.attributes = a.attributes[:i] + a.attributes[i+1:]
164 }
165 case "24":
166 if i := strings.IndexRune(a.attributes, 'u'); i >= 0 {
167 a.attributes = a.attributes[:i] + a.attributes[i+1:]
168 }
169 case "25":
170 if i := strings.IndexRune(a.attributes, 'l'); i >= 0 {
171 a.attributes = a.attributes[:i] + a.attributes[i+1:]
172 }
173 case "30", "31", "32", "33", "34", "35", "36", "37":
174 colorNumber, _ := strconv.Atoi(field)
175 foreground = lookupColor(colorNumber - 30)
176 case "39":
177 foreground = "-"
178 case "40", "41", "42", "43", "44", "45", "46", "47":
179 colorNumber, _ := strconv.Atoi(field)
180 background = lookupColor(colorNumber - 40)
181 case "49":
182 background = "-"
183 case "90", "91", "92", "93", "94", "95", "96", "97":
184 colorNumber, _ := strconv.Atoi(field)
185 foreground = lookupColor(colorNumber - 82)
186 case "100", "101", "102", "103", "104", "105", "106", "107":
187 colorNumber, _ := strconv.Atoi(field)
188 background = lookupColor(colorNumber - 92)
189 case "38", "48":
190 var color string
191 if len(fields) > index+1 {
192 if fields[index+1] == "5" && len(fields) > index+2 {
193 colorNumber, _ := strconv.Atoi(fields[index+2])
194 if colorNumber <= 15 {
195 color = lookupColor(colorNumber)
196 } else if colorNumber <= 231 {
197 red := (colorNumber - 16) / 36
198 green := ((colorNumber - 16) / 6) % 6
199 blue := (colorNumber - 16) % 6
200 color = fmt.Sprintf("#%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
201 } else if colorNumber <= 255 {
202 grey := 255 * (colorNumber - 232) / 23
203 color = fmt.Sprintf("#%02x%02x%02x", grey, grey, grey)
204 }
205 } else if fields[index+1] == "2" && len(fields) > index+4 {
206 red, _ := strconv.Atoi(fields[index+2])
207 green, _ := strconv.Atoi(fields[index+3])
208 blue, _ := strconv.Atoi(fields[index+4])
209 color = fmt.Sprintf("#%02x%02x%02x", red, green, blue)
210 }
211 }
212 if len(color) > 0 {
213 if field == "38" {
214 foreground = color
215 } else {
216 background = color
217 }
218 }
219 break FieldLoop
220 }
221 }
222 var colon string
223 if len(a.attributes) > 0 {
224 colon = ":"
225 }
226 if len(foreground) > 0 || len(background) > 0 || len(a.attributes) > 0 {
227 fmt.Fprintf(a.buffer, "[%s:%s%s%s]", foreground, background, colon, a.attributes)
228 }
229 }
230 a.state = ansiText
231 default:
232 a.state = ansiText
233 }
234
235
236 case ansiSubstring:
237 if r == 27 {
238 a.state = ansiEscape
239 }
240
241
242 default:
243 if r == 27 {
244
245 a.state = ansiEscape
246 } else {
247
248 if _, err := a.buffer.WriteRune(r); err != nil {
249 return 0, err
250 }
251 }
252 }
253 }
254
255
256 n, err := a.buffer.WriteTo(a.Writer)
257 if err != nil {
258 return int(n), err
259 }
260 return len(text), nil
261 }
262
263
264
265 func TranslateANSI(text string) string {
266 var buffer bytes.Buffer
267 writer := ANSIWriter(&buffer)
268 writer.Write([]byte(text))
269 return buffer.String()
270 }
271
View as plain text