1// Copyright 2014 The gocui Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gocui
6
7import (
8 "errors"
9 "strconv"
10)
11
12type escapeInterpreter struct {
13 state escapeState
14 curch rune
15 csiParam []string
16 curFgColor, curBgColor Attribute
17}
18
19type escapeState int
20
21const (
22 stateNone escapeState = iota
23 stateEscape
24 stateCSI
25 stateParams
26)
27
28var (
29 errNotCSI = errors.New("Not a CSI escape sequence")
30 errCSINotANumber = errors.New("CSI escape sequence was expecting a number or a ;")
31 errCSIParseError = errors.New("CSI escape sequence parsing error")
32 errCSITooLong = errors.New("CSI escape sequence is too long")
33)
34
35// runes in case of error will output the non-parsed runes as a string.
36func (ei *escapeInterpreter) runes() []rune {
37 switch ei.state {
38 case stateNone:
39 return []rune{0x1b}
40 case stateEscape:
41 return []rune{0x1b, ei.curch}
42 case stateCSI:
43 return []rune{0x1b, '[', ei.curch}
44 case stateParams:
45 ret := []rune{0x1b, '['}
46 for _, s := range ei.csiParam {
47 ret = append(ret, []rune(s)...)
48 ret = append(ret, ';')
49 }
50 return append(ret, ei.curch)
51 }
52 return nil
53}
54
55// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
56// terminal escape sequences.
57func newEscapeInterpreter() *escapeInterpreter {
58 ei := &escapeInterpreter{
59 state: stateNone,
60 curFgColor: ColorDefault,
61 curBgColor: ColorDefault,
62 }
63 return ei
64}
65
66// reset sets the escapeInterpreter in initial state.
67func (ei *escapeInterpreter) reset() {
68 ei.state = stateNone
69 ei.curFgColor = ColorDefault
70 ei.curBgColor = ColorDefault
71 ei.csiParam = nil
72}
73
74// paramToColor returns an attribute given a terminfo coloring.
75func paramToColor(p int) Attribute {
76 switch p {
77 case 0:
78 return ColorBlack
79 case 1:
80 return ColorRed
81 case 2:
82 return ColorGreen
83 case 3:
84 return ColorYellow
85 case 4:
86 return ColorBlue
87 case 5:
88 return ColorMagenta
89 case 6:
90 return ColorCyan
91 case 7:
92 return ColorWhite
93 }
94 return ColorDefault
95}
96
97// parseOne parses a rune. If isEscape is true, it means that the rune is part
98// of an escape sequence, and as such should not be printed verbatim. Otherwise,
99// it's not an escape sequence.
100func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
101 // Sanity checks to make sure we're not parsing something totally bogus.
102 if len(ei.csiParam) > 20 {
103 return false, errCSITooLong
104 }
105 if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
106 return false, errCSITooLong
107 }
108 ei.curch = ch
109 switch ei.state {
110 case stateNone:
111 if ch == 0x1b {
112 ei.state = stateEscape
113 return true, nil
114 }
115 return false, nil
116 case stateEscape:
117 if ch == '[' {
118 ei.state = stateCSI
119 return true, nil
120 }
121 return false, errNotCSI
122 case stateCSI:
123 if ch >= '0' && ch <= '9' {
124 ei.state = stateParams
125 ei.csiParam = append(ei.csiParam, string(ch))
126 return true, nil
127 }
128 return false, errCSINotANumber
129 case stateParams:
130 switch {
131 case ch >= '0' && ch <= '9':
132 ei.csiParam[len(ei.csiParam)-1] += string(ch)
133 return true, nil
134 case ch == ';':
135 ei.csiParam = append(ei.csiParam, "")
136 return true, nil
137 case ch == 'm':
138 if len(ei.csiParam) < 1 {
139 return false, errCSIParseError
140 }
141 for _, param := range ei.csiParam {
142 p, err := strconv.Atoi(param)
143 if err != nil {
144 return false, errCSIParseError
145 }
146 switch {
147 case p >= 30 && p <= 37:
148 ei.curFgColor = paramToColor(p - 30)
149 case p >= 40 && p <= 47:
150 ei.curBgColor = paramToColor(p - 40)
151 case p == 1:
152 ei.curFgColor |= AttrBold
153 case p == 4:
154 ei.curFgColor |= AttrUnderline
155 case p == 7:
156 ei.curFgColor |= AttrReverse
157 case p == 0 || p == 39:
158 ei.curFgColor = ColorDefault
159 ei.curBgColor = ColorDefault
160 }
161 }
162 ei.state = stateNone
163 ei.csiParam = nil
164 return true, nil
165 }
166 }
167 return false, nil
168}