escape_state.go

  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}