/
ansi.go
335 lines (288 loc) · 7.42 KB
/
ansi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Package ansi implements the ANSI VT100 control set.
// Please refer to http://www.termsys.demon.co.uk/vtansi.htm
package ansi
import (
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
)
// Ansi represents a wrapped io.ReadWriter.
// It will read the stream, parse and remove ANSI report codes
// and place them on the Reports queue.
type Ansi struct {
rw io.ReadWriter
rerr error
rbuff chan []byte
Reports chan *Report
}
// Wrap an io.ReadWriter (like a net.Conn) to
// easily read and write control codes
func Wrap(rw io.ReadWriter) *Ansi {
a := &Ansi{}
a.rw = rw
a.rbuff = make(chan []byte)
a.Reports = make(chan *Report)
go a.read()
return a
}
var reportCode = regexp.MustCompile(`\[([^a-zA-Z]*)(0c|0n|3n|R)`)
// reads the underlying ReadWriter for real,
// extracts the ansi codes, places the rest
// in the read buffer
func (a *Ansi) read() {
buff := make([]byte, 0xffff)
for {
n, err := a.rw.Read(buff)
if err != nil {
a.rerr = err
close(a.rbuff)
break
}
var src = buff[:n]
var dst []byte
//contain ansi codes?
m := reportCode.FindAllStringSubmatchIndex(string(src), -1)
if len(m) == 0 {
dst = make([]byte, n)
copy(dst, src)
} else {
for _, i := range m {
//slice off ansi code body and trailing char
a.parse(string(src[i[2]:i[3]]), string(src[i[4]:i[5]]))
//add surrounding bits to dst buffer
dst = append(dst, src[:i[0]]...)
dst = append(dst, src[i[1]:]...)
}
if len(dst) == 0 {
continue
}
}
a.rbuff <- dst
}
}
// Report Device Code <ESC>[{code}0c
// Report Device OK <ESC>[0n
// Report Device Failure <ESC>[3n
// Report Cursor Position <ESC>[{ROW};{COLUMN}R
func (a *Ansi) parse(body, char string) {
r := &Report{}
switch char {
case "0c":
r.Type = Code
r.Code, _ = strconv.Atoi(body)
case "0n":
r.Type = OK
case "3n":
r.Type = Failure
case "R":
r.Type = Position
pair := strings.Split(body, ";")
r.Pos.Col, _ = strconv.Atoi(pair[1])
r.Pos.Row, _ = strconv.Atoi(pair[0])
default:
return
}
// fmt.Printf("parsed report: %+v", r)
a.Reports <- r
}
// Reads the underlying ReadWriter
func (a *Ansi) Read(dest []byte) (n int, err error) {
//It doesn't really read the underlying ReadWriter :)
if a.rerr != nil {
return 0, a.rerr
}
src, open := <-a.rbuff
if !open {
return 0, a.rerr
}
return copy(dest, src), nil
}
// Writes the underlying ReadWriter
func (a *Ansi) Write(p []byte) (n int, err error) {
return a.rw.Write(p)
}
// Close the underlying ReadWriter
func (a *Ansi) Close() error {
c, ok := a.rw.(io.Closer)
if !ok {
return errors.New("Provided ReadWriter is not a Closer")
}
return c.Close()
}
//==============================
type ReportType int
const (
Code ReportType = iota
OK
Failure
Position
)
type Report struct {
Type ReportType
Code int
Pos struct {
Row, Col int
}
}
//==============================
const Esc = byte(27)
var QueryCode = []byte{Esc, '[', 'c'}
var QueryDeviceStatus = []byte{Esc, '[', '5', 'n'}
var QueryCursorPosition = []byte{Esc, '[', '6', 'n'}
func (a *Ansi) QueryCursorPosition() {
a.Write(QueryCursorPosition)
}
var ResetDevice = []byte{Esc, 'c'}
var EnableLineWrap = []byte{Esc, '[', '7', 'h'}
func (a *Ansi) EnableLineWrap() {
a.Write(DisableLineWrap)
}
var DisableLineWrap = []byte{Esc, '[', '7', 'l'}
func (a *Ansi) DisableLineWrap() {
a.Write(DisableLineWrap)
}
var FontSetG0 = []byte{Esc, '('}
var FontSetG1 = []byte{Esc, ')'}
// Goto various positions:
// Cursor Home <ESC>[{ROW};{COLUMN}H
// Cursor Up <ESC>[{COUNT}A
// Cursor Down <ESC>[{COUNT}B
// Cursor Forward <ESC>[{COUNT}C
// Cursor Backward <ESC>[{COUNT}D
// Force Cursor Position <ESC>[{ROW};{COLUMN}f
func Goto(r, c uint16) []byte {
rb := []byte(strconv.Itoa(int(r)))
cb := []byte(strconv.Itoa(int(c)))
b := append([]byte{Esc, '['}, rb...)
b = append(b, ';')
b = append(b, cb...)
b = append(b, 'f')
return b
}
func (a *Ansi) Goto(r, c uint16) {
a.Write(Goto(r, c))
}
var SaveCursor = []byte{Esc, '[', 's'}
var UnsaveCursor = []byte{Esc, '[', 'u'}
var SaveAttrCursor = []byte{Esc, '7'}
var RestoreAttrCursor = []byte{Esc, '8'}
var CursorHide = []byte{Esc, '[', '?', '2', '5', 'l'}
func (a *Ansi) CursorHide() {
a.Write(CursorHide)
}
var CursorShow = []byte{Esc, '[', '?', '2', '5', 'h'}
func (a *Ansi) CursorShow() {
a.Write(CursorShow)
}
var ScrollScreen = []byte{Esc, '[', 'r'}
var ScrollDown = []byte{Esc, 'D'}
var ScrollUp = []byte{Esc, 'M'}
func Scroll(start, end uint16) []byte {
return []byte(string(Esc) + fmt.Sprintf("[%d;%dr", start, end))
}
// Tab Control
var SetTab = []byte{Esc, 'H'}
var ClearTab = []byte{Esc, '[', 'g'}
var ClearAllTabs = []byte{Esc, '[', '3', 'g'}
var EraseEndLine = []byte{Esc, '[', 'K'}
var EraseStartLine = []byte{Esc, '[', '1', 'K'}
var EraseLine = []byte{Esc, '[', '2', 'K'}
var EraseDown = []byte{Esc, '[', 'J'}
var EraseUp = []byte{Esc, '[', '1', 'J'}
var EraseScreen = []byte{Esc, '[', '2', 'J'}
func (a *Ansi) EraseScreen() {
a.Write(EraseScreen)
}
// Printing
var PrintScreen = []byte{Esc, '[', 'i'}
var PrintLine = []byte{Esc, '[', '1', 'i'}
var StopPrintLog = []byte{Esc, '[', '4', 'i'}
var StartPrintLog = []byte{Esc, '[', '5', 'i'}
// Set Key Definition <ESC>[{key};"{ascii}"p
// Sets multiple display attribute settings. The following lists standard attributes:
type Attribute string
const (
//formating
Reset Attribute = "0"
Bright Attribute = "1"
Dim Attribute = "2"
Italic Attribute = "3"
Underscore Attribute = "4"
Blink Attribute = "5"
Reverse Attribute = "7"
Hidden Attribute = "8"
//foreground colors
Black Attribute = "30"
Red Attribute = "31"
Green Attribute = "32"
Yellow Attribute = "33"
Blue Attribute = "34"
Magenta Attribute = "35"
Cyan Attribute = "36"
White Attribute = "37"
//background colors
BlackBG Attribute = "40"
RedBG Attribute = "41"
GreenBG Attribute = "42"
YellowBG Attribute = "43"
BlueBG Attribute = "44"
MagentaBG Attribute = "45"
CyanBG Attribute = "46"
WhiteBG Attribute = "47"
// Default Colors
Default Attribute = "39"
DefaultBG Attribute = "49"
)
// String joins: attribute, string, reset, and converts to a string
func (a Attribute) String(s string) string {
return string(a.Join(s, Reset))
}
// Wrap joins: attribute, string, another attribute
func (a Attribute) Join(s string, b Attribute) []byte {
return append(Set(a), append([]byte(s), Set(b)...)...)
}
// Set attributes
func Set(attrs ...Attribute) []byte {
s := make([]string, len(attrs))
for i, a := range attrs {
s[i] = string(a)
}
b := []byte(strings.Join(s, ";"))
b = append(b, 'm')
return append([]byte{Esc, '['}, b...)
}
// Set Attribute Mode <ESC>[{attr1};...;{attrn}m
func (a *Ansi) Set(attrs ...Attribute) {
a.Write(Set(attrs...))
}
var (
ResetBytes = Set(Reset)
BrightBytes = Set(Bright)
DimBytes = Set(Dim)
ItalicBytes = Set(Italic)
UnderscoreBytes = Set(Underscore)
BlinkBytes = Set(Blink)
ReverseBytes = Set(Reverse)
HiddenBytes = Set(Hidden)
//foreground colors
BlackBytes = Set(Black)
RedBytes = Set(Red)
GreenBytes = Set(Green)
YellowBytes = Set(Yellow)
BlueBytes = Set(Blue)
MagentaBytes = Set(Magenta)
CyanBytes = Set(Cyan)
WhiteBytes = Set(White)
//background colors
BlackBGBytes = Set(BlackBG)
RedBGBytes = Set(RedBG)
GreenBGBytes = Set(GreenBG)
YellowBGBytes = Set(YellowBG)
BlueBGBytes = Set(BlueBG)
MagentaBGBytes = Set(MagentaBG)
CyanBGBytes = Set(CyanBG)
WhiteBGBytes = Set(WhiteBG)
)