forked from charithe/terminfo
-
Notifications
You must be signed in to change notification settings - Fork 11
/
terminfo.go
479 lines (449 loc) · 12.6 KB
/
terminfo.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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
// Package terminfo implements reading terminfo files in pure go.
package terminfo
//go:generate go run gen.go
import (
"io"
"io/ioutil"
"path"
"strconv"
"strings"
)
// Error is a terminfo error.
type Error string
// Error satisfies the error interface.
func (err Error) Error() string {
return string(err)
}
const (
// ErrInvalidFileSize is the invalid file size error.
ErrInvalidFileSize Error = "invalid file size"
// ErrUnexpectedFileEnd is the unexpected file end error.
ErrUnexpectedFileEnd Error = "unexpected file end"
// ErrInvalidStringTable is the invalid string table error.
ErrInvalidStringTable Error = "invalid string table"
// ErrInvalidMagic is the invalid magic error.
ErrInvalidMagic Error = "invalid magic"
// ErrInvalidHeader is the invalid header error.
ErrInvalidHeader Error = "invalid header"
// ErrInvalidNames is the invalid names error.
ErrInvalidNames Error = "invalid names"
// ErrInvalidExtendedHeader is the invalid extended header error.
ErrInvalidExtendedHeader Error = "invalid extended header"
// ErrEmptyTermName is the empty term name error.
ErrEmptyTermName Error = "empty term name"
// ErrDatabaseDirectoryNotFound is the database directory not found error.
ErrDatabaseDirectoryNotFound Error = "database directory not found"
// ErrFileNotFound is the file not found error.
ErrFileNotFound Error = "file not found"
// ErrInvalidTermProgramVersion is the invalid TERM_PROGRAM_VERSION error.
ErrInvalidTermProgramVersion Error = "invalid TERM_PROGRAM_VERSION"
)
// Terminfo describes a terminal's capabilities.
type Terminfo struct {
// File is the original source file.
File string
// Names are the provided cap names.
Names []string
// Bools are the bool capabilities.
Bools map[int]bool
// BoolsM are the missing bool capabilities.
BoolsM map[int]bool
// Nums are the num capabilities.
Nums map[int]int
// NumsM are the missing num capabilities.
NumsM map[int]bool
// Strings are the string capabilities.
Strings map[int][]byte
// StringsM are the missing string capabilities.
StringsM map[int]bool
// ExtBools are the extended bool capabilities.
ExtBools map[int]bool
// ExtBoolsNames is the map of extended bool capabilities to their index.
ExtBoolNames map[int][]byte
// ExtNums are the extended num capabilities.
ExtNums map[int]int
// ExtNumsNames is the map of extended num capabilities to their index.
ExtNumNames map[int][]byte
// ExtStrings are the extended string capabilities.
ExtStrings map[int][]byte
// ExtStringsNames is the map of extended string capabilities to their index.
ExtStringNames map[int][]byte
}
// Decode decodes the terminfo data contained in buf.
func Decode(buf []byte) (*Terminfo, error) {
var err error
// check max file length
if len(buf) >= maxFileLength {
return nil, ErrInvalidFileSize
}
d := &decoder{
buf: buf,
n: len(buf),
}
// read header
h, err := d.readInts(6, 16)
if err != nil {
return nil, err
}
var numWidth int
// check magic
switch {
case h[fieldMagic] == magic:
numWidth = 16
case h[fieldMagic] == magicExtended:
numWidth = 32
default:
return nil, ErrInvalidMagic
}
// check header
if hasInvalidCaps(h) {
return nil, ErrInvalidHeader
}
// check remaining length
if d.n-d.pos < capLength(h) {
return nil, ErrUnexpectedFileEnd
}
// read names
names, err := d.readBytes(h[fieldNameSize])
if err != nil {
return nil, err
}
// check name is terminated properly
i := findNull(names, 0)
if i == -1 {
return nil, ErrInvalidNames
}
names = names[:i]
// read bool caps
bools, boolsM, err := d.readBools(h[fieldBoolCount])
if err != nil {
return nil, err
}
// read num caps
nums, numsM, err := d.readNums(h[fieldNumCount], numWidth)
if err != nil {
return nil, err
}
// read string caps
strs, strsM, err := d.readStrings(h[fieldStringCount], h[fieldTableSize])
if err != nil {
return nil, err
}
ti := &Terminfo{
Names: strings.Split(string(names), "|"),
Bools: bools,
BoolsM: boolsM,
Nums: nums,
NumsM: numsM,
Strings: strs,
StringsM: strsM,
}
// at the end of file, so no extended caps
if d.pos >= d.n {
return ti, nil
}
// decode extended header
eh, err := d.readInts(5, 16)
if err != nil {
return nil, err
}
// check extended offset field
if hasInvalidExtOffset(eh) {
return nil, ErrInvalidExtendedHeader
}
// check extended cap lengths
if d.n-d.pos != extCapLength(eh, numWidth) {
return nil, ErrInvalidExtendedHeader
}
// read extended bool caps
ti.ExtBools, _, err = d.readBools(eh[fieldExtBoolCount])
if err != nil {
return nil, err
}
// read extended num caps
ti.ExtNums, _, err = d.readNums(eh[fieldExtNumCount], numWidth)
if err != nil {
return nil, err
}
// read extended string data table indexes
extIndexes, err := d.readInts(eh[fieldExtOffsetCount], 16)
if err != nil {
return nil, err
}
// read string data table
extData, err := d.readBytes(eh[fieldExtTableSize])
if err != nil {
return nil, err
}
// precautionary check that exactly at end of file
if d.pos != d.n {
return nil, ErrUnexpectedFileEnd
}
var last int
// read extended string caps
ti.ExtStrings, last, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
if err != nil {
return nil, err
}
extIndexes, extData = extIndexes[eh[fieldExtStringCount]:], extData[last:]
// read extended bool names
ti.ExtBoolNames, _, err = readStrings(extIndexes, extData, eh[fieldExtBoolCount])
if err != nil {
return nil, err
}
extIndexes = extIndexes[eh[fieldExtBoolCount]:]
// read extended num names
ti.ExtNumNames, _, err = readStrings(extIndexes, extData, eh[fieldExtNumCount])
if err != nil {
return nil, err
}
extIndexes = extIndexes[eh[fieldExtNumCount]:]
// read extended string names
ti.ExtStringNames, _, err = readStrings(extIndexes, extData, eh[fieldExtStringCount])
if err != nil {
return nil, err
}
// extIndexes = extIndexes[eh[fieldExtStringCount]:]
return ti, nil
}
// Open reads the terminfo file name from the specified directory dir.
func Open(dir, name string) (*Terminfo, error) {
var err error
var buf []byte
var filename string
for _, f := range []string{
path.Join(dir, name[0:1], name),
path.Join(dir, strconv.FormatUint(uint64(name[0]), 16), name),
} {
buf, err = ioutil.ReadFile(f)
if err == nil {
filename = f
break
}
}
if buf == nil {
return nil, ErrFileNotFound
}
// decode
ti, err := Decode(buf)
if err != nil {
return nil, err
}
// save original file name
ti.File = filename
// add to cache
termCache.Lock()
for _, n := range ti.Names {
termCache.db[n] = ti
}
termCache.Unlock()
return ti, nil
}
// boolCaps returns all bool and extended capabilities using f to format the
// index key.
func (ti *Terminfo) boolCaps(f func(int) string, extended bool) map[string]bool {
m := make(map[string]bool, len(ti.Bools)+len(ti.ExtBools))
if !extended {
for k, v := range ti.Bools {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtBools {
m[string(ti.ExtBoolNames[k])] = v
}
}
return m
}
// BoolCaps returns all bool capabilities.
func (ti *Terminfo) BoolCaps() map[string]bool {
return ti.boolCaps(BoolCapName, false)
}
// BoolCapsShort returns all bool capabilities, using the short name as the
// index.
func (ti *Terminfo) BoolCapsShort() map[string]bool {
return ti.boolCaps(BoolCapNameShort, false)
}
// ExtBoolCaps returns all extended bool capabilities.
func (ti *Terminfo) ExtBoolCaps() map[string]bool {
return ti.boolCaps(BoolCapName, true)
}
// ExtBoolCapsShort returns all extended bool capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtBoolCapsShort() map[string]bool {
return ti.boolCaps(BoolCapNameShort, true)
}
// numCaps returns all num and extended capabilities using f to format the
// index key.
func (ti *Terminfo) numCaps(f func(int) string, extended bool) map[string]int {
m := make(map[string]int, len(ti.Nums)+len(ti.ExtNums))
if !extended {
for k, v := range ti.Nums {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtNums {
m[string(ti.ExtNumNames[k])] = v
}
}
return m
}
// NumCaps returns all num capabilities.
func (ti *Terminfo) NumCaps() map[string]int {
return ti.numCaps(NumCapName, false)
}
// NumCapsShort returns all num capabilities, using the short name as the
// index.
func (ti *Terminfo) NumCapsShort() map[string]int {
return ti.numCaps(NumCapNameShort, false)
}
// ExtNumCaps returns all extended num capabilities.
func (ti *Terminfo) ExtNumCaps() map[string]int {
return ti.numCaps(NumCapName, true)
}
// ExtNumCapsShort returns all extended num capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtNumCapsShort() map[string]int {
return ti.numCaps(NumCapNameShort, true)
}
// stringCaps returns all string and extended capabilities using f to format the
// index key.
func (ti *Terminfo) stringCaps(f func(int) string, extended bool) map[string][]byte {
m := make(map[string][]byte, len(ti.Strings)+len(ti.ExtStrings))
if !extended {
for k, v := range ti.Strings {
m[f(k)] = v
}
} else {
for k, v := range ti.ExtStrings {
m[string(ti.ExtStringNames[k])] = v
}
}
return m
}
// StringCaps returns all string capabilities.
func (ti *Terminfo) StringCaps() map[string][]byte {
return ti.stringCaps(StringCapName, false)
}
// StringCapsShort returns all string capabilities, using the short name as the
// index.
func (ti *Terminfo) StringCapsShort() map[string][]byte {
return ti.stringCaps(StringCapNameShort, false)
}
// ExtStringCaps returns all extended string capabilities.
func (ti *Terminfo) ExtStringCaps() map[string][]byte {
return ti.stringCaps(StringCapName, true)
}
// ExtStringCapsShort returns all extended string capabilities, using the short
// name as the index.
func (ti *Terminfo) ExtStringCapsShort() map[string][]byte {
return ti.stringCaps(StringCapNameShort, true)
}
// Has determines if the bool cap i is present.
func (ti *Terminfo) Has(i int) bool {
return ti.Bools[i]
}
// Num returns the num cap i, or -1 if not present.
func (ti *Terminfo) Num(i int) int {
n, ok := ti.Nums[i]
if !ok {
return -1
}
return n
}
// Printf formats the string cap i, interpolating parameters v.
func (ti *Terminfo) Printf(i int, v ...interface{}) string {
return Printf(ti.Strings[i], v...)
}
// Fprintf prints the string cap i to writer w, interpolating parameters v.
func (ti *Terminfo) Fprintf(w io.Writer, i int, v ...interface{}) {
Fprintf(w, ti.Strings[i], v...)
}
// Color takes a foreground and background color and returns string that sets
// them for this terminal.
func (ti *Terminfo) Colorf(fg, bg int, str string) string {
maxColors := int(ti.Nums[MaxColors])
// map bright colors to lower versions if the color table only holds 8.
if maxColors == 8 {
if fg > 7 && fg < 16 {
fg -= 8
}
if bg > 7 && bg < 16 {
bg -= 8
}
}
var s string
if maxColors > fg && fg >= 0 {
s += ti.Printf(SetAForeground, fg)
}
if maxColors > bg && bg >= 0 {
s += ti.Printf(SetABackground, bg)
}
return s + str + ti.Printf(ExitAttributeMode)
}
// Goto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (ti *Terminfo) Goto(row, col int) string {
return Printf(ti.Strings[CursorAddress], row, col)
}
// Puts emits the string to the writer, but expands inline padding indications
// (of the form $<[delay]> where [delay] is msec) to a suitable number of
// padding characters (usually null bytes) based upon the supplied baud. At
// high baud rates, more padding characters will be inserted.
/*func (ti *Terminfo) Puts(w io.Writer, s string, lines, baud int) (int, error) {
var err error
for {
start := strings.Index(s, "$<")
if start == -1 {
// most strings don't need padding, which is good news!
return io.WriteString(w, s)
}
end := strings.Index(s, ">")
if end == -1 {
// unterminated... just emit bytes unadulterated.
return io.WriteString(w, "$<"+s)
}
var c int
c, err = io.WriteString(w, s[:start])
if err != nil {
return n + c, err
}
n += c
s = s[start+2:]
val := s[:end]
s = s[end+1:]
var ms int
var dot, mandatory, asterisk bool
unit := 1000
for _, ch := range val {
switch {
case ch >= '0' && ch <= '9':
ms = (ms * 10) + int(ch-'0')
if dot {
unit *= 10
}
case ch == '.' && !dot:
dot = true
case ch == '*' && !asterisk:
ms *= lines
asterisk = true
case ch == '/':
mandatory = true
default:
break
}
}
z, pad := ((baud/8)/unit)*ms, ti.Strings[PadChar]
b := make([]byte, len(pad)*z)
for bp := copy(b, pad); bp < len(b); bp *= 2 {
copy(b[bp:], b[:bp])
}
if (!ti.Bools[XonXoff] && baud > int(ti.Nums[PaddingBaudRate])) || mandatory {
c, err = w.Write(b)
if err != nil {
return n + c, err
}
n += c
}
}
return n, nil
}*/