1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package terminfo
16
17 import (
18 "bytes"
19 "errors"
20 "fmt"
21 "io"
22 "os"
23 "strconv"
24 "strings"
25 "sync"
26 "time"
27 )
28
29 var (
30
31
32
33
34
35
36
37 ErrTermNotFound = errors.New("terminal entry not found")
38 )
39
40
41
42
43 type Terminfo struct {
44 Name string
45 Aliases []string
46 Columns int
47 Lines int
48 Colors int
49 Bell string
50 Clear string
51 EnterCA string
52 ExitCA string
53 ShowCursor string
54 HideCursor string
55 AttrOff string
56 Underline string
57 Bold string
58 Blink string
59 Reverse string
60 Dim string
61 Italic string
62 EnterKeypad string
63 ExitKeypad string
64 SetFg string
65 SetBg string
66 ResetFgBg string
67 SetCursor string
68 CursorBack1 string
69 CursorUp1 string
70 PadChar string
71 KeyBackspace string
72 KeyF1 string
73 KeyF2 string
74 KeyF3 string
75 KeyF4 string
76 KeyF5 string
77 KeyF6 string
78 KeyF7 string
79 KeyF8 string
80 KeyF9 string
81 KeyF10 string
82 KeyF11 string
83 KeyF12 string
84 KeyF13 string
85 KeyF14 string
86 KeyF15 string
87 KeyF16 string
88 KeyF17 string
89 KeyF18 string
90 KeyF19 string
91 KeyF20 string
92 KeyF21 string
93 KeyF22 string
94 KeyF23 string
95 KeyF24 string
96 KeyF25 string
97 KeyF26 string
98 KeyF27 string
99 KeyF28 string
100 KeyF29 string
101 KeyF30 string
102 KeyF31 string
103 KeyF32 string
104 KeyF33 string
105 KeyF34 string
106 KeyF35 string
107 KeyF36 string
108 KeyF37 string
109 KeyF38 string
110 KeyF39 string
111 KeyF40 string
112 KeyF41 string
113 KeyF42 string
114 KeyF43 string
115 KeyF44 string
116 KeyF45 string
117 KeyF46 string
118 KeyF47 string
119 KeyF48 string
120 KeyF49 string
121 KeyF50 string
122 KeyF51 string
123 KeyF52 string
124 KeyF53 string
125 KeyF54 string
126 KeyF55 string
127 KeyF56 string
128 KeyF57 string
129 KeyF58 string
130 KeyF59 string
131 KeyF60 string
132 KeyF61 string
133 KeyF62 string
134 KeyF63 string
135 KeyF64 string
136 KeyInsert string
137 KeyDelete string
138 KeyHome string
139 KeyEnd string
140 KeyHelp string
141 KeyPgUp string
142 KeyPgDn string
143 KeyUp string
144 KeyDown string
145 KeyLeft string
146 KeyRight string
147 KeyBacktab string
148 KeyExit string
149 KeyClear string
150 KeyPrint string
151 KeyCancel string
152 Mouse string
153 AltChars string
154 EnterAcs string
155 ExitAcs string
156 EnableAcs string
157 KeyShfRight string
158 KeyShfLeft string
159 KeyShfHome string
160 KeyShfEnd string
161 KeyShfInsert string
162 KeyShfDelete string
163
164
165
166
167
168
169
170 StrikeThrough string
171 SetFgBg string
172 SetFgBgRGB string
173 SetFgRGB string
174 SetBgRGB string
175 KeyShfUp string
176 KeyShfDown string
177 KeyShfPgUp string
178 KeyShfPgDn string
179 KeyCtrlUp string
180 KeyCtrlDown string
181 KeyCtrlRight string
182 KeyCtrlLeft string
183 KeyMetaUp string
184 KeyMetaDown string
185 KeyMetaRight string
186 KeyMetaLeft string
187 KeyAltUp string
188 KeyAltDown string
189 KeyAltRight string
190 KeyAltLeft string
191 KeyCtrlHome string
192 KeyCtrlEnd string
193 KeyMetaHome string
194 KeyMetaEnd string
195 KeyAltHome string
196 KeyAltEnd string
197 KeyAltShfUp string
198 KeyAltShfDown string
199 KeyAltShfLeft string
200 KeyAltShfRight string
201 KeyMetaShfUp string
202 KeyMetaShfDown string
203 KeyMetaShfLeft string
204 KeyMetaShfRight string
205 KeyCtrlShfUp string
206 KeyCtrlShfDown string
207 KeyCtrlShfLeft string
208 KeyCtrlShfRight string
209 KeyCtrlShfHome string
210 KeyCtrlShfEnd string
211 KeyAltShfHome string
212 KeyAltShfEnd string
213 KeyMetaShfHome string
214 KeyMetaShfEnd string
215 EnablePaste string
216 DisablePaste string
217 PasteStart string
218 PasteEnd string
219 Modifiers int
220 InsertChar string
221 AutoMargin bool
222 TrueColor bool
223 CursorDefault string
224 CursorBlinkingBlock string
225 CursorSteadyBlock string
226 CursorBlinkingUnderline string
227 CursorSteadyUnderline string
228 CursorBlinkingBar string
229 CursorSteadyBar string
230 EnterUrl string
231 ExitUrl string
232 SetWindowSize string
233 }
234
235 const (
236 ModifiersNone = 0
237 ModifiersXTerm = 1
238 )
239
240 type stack []interface{}
241
242 func (st stack) Push(v interface{}) stack {
243 if b, ok := v.(bool); ok {
244 if b {
245 return append(st, 1)
246 } else {
247 return append(st, 0)
248 }
249 }
250 return append(st, v)
251 }
252
253 func (st stack) PopString() (string, stack) {
254 if len(st) > 0 {
255 e := st[len(st)-1]
256 var s string
257 switch v := e.(type) {
258 case int:
259 s = strconv.Itoa(v)
260 case string:
261 s = v
262 }
263 return s, st[:len(st)-1]
264 }
265 return "", st
266
267 }
268 func (st stack) PopInt() (int, stack) {
269 if len(st) > 0 {
270 e := st[len(st)-1]
271 var i int
272 switch v := e.(type) {
273 case int:
274 i = v
275 case string:
276 i, _ = strconv.Atoi(v)
277 }
278 return i, st[:len(st)-1]
279 }
280 return 0, st
281 }
282
283
284 var svars [26]string
285
286 type paramsBuffer struct {
287 out bytes.Buffer
288 buf bytes.Buffer
289 }
290
291
292
293
294 func (pb *paramsBuffer) Start(s string) {
295 pb.out.Reset()
296 pb.buf.Reset()
297 pb.buf.WriteString(s)
298 }
299
300
301 func (pb *paramsBuffer) End() string {
302 s := pb.out.String()
303 return s
304 }
305
306
307 func (pb *paramsBuffer) NextCh() (byte, error) {
308 return pb.buf.ReadByte()
309 }
310
311
312 func (pb *paramsBuffer) PutCh(ch byte) {
313 pb.out.WriteByte(ch)
314 }
315
316
317 func (pb *paramsBuffer) PutString(s string) {
318 pb.out.WriteString(s)
319 }
320
321
322
323
324 func (t *Terminfo) TParm(s string, p ...interface{}) string {
325 var stk stack
326 var a string
327 var ai, bi int
328 var dvars [26]string
329 var params [9]interface{}
330 var pb = ¶msBuffer{}
331
332 pb.Start(s)
333
334
335
336 for i := 0; i < len(params) && i < len(p); i++ {
337 params[i] = p[i]
338 }
339
340 const (
341 emit = iota
342 toEnd
343 toElse
344 )
345
346 skip := emit
347
348 for {
349
350 ch, err := pb.NextCh()
351 if err != nil {
352 break
353 }
354
355 if ch != '%' {
356 if skip == emit {
357 pb.PutCh(ch)
358 }
359 continue
360 }
361
362 ch, err = pb.NextCh()
363 if err != nil {
364
365 break
366 }
367 if skip == toEnd {
368 if ch == ';' {
369 skip = emit
370 }
371 continue
372 } else if skip == toElse {
373 if ch == 'e' || ch == ';' {
374 skip = emit
375 }
376 continue
377 }
378
379 switch ch {
380 case '%':
381 pb.PutCh(ch)
382
383 case 'i':
384 if i, ok := params[0].(int); ok {
385 params[0] = i + 1
386 }
387 if i, ok := params[1].(int); ok {
388 params[1] = i + 1
389 }
390
391 case 's':
392
393
394
395 a, stk = stk.PopString()
396 pb.PutString(a)
397
398 case 'c':
399
400 ai, stk = stk.PopInt()
401 pb.PutCh(byte(ai))
402
403 case 'd':
404 ai, stk = stk.PopInt()
405 pb.PutString(strconv.Itoa(ai))
406
407 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'x', 'X', 'o', ':':
408
409
410
411
412 f := "%"
413 if ch == ':' {
414 ch, _ = pb.NextCh()
415 }
416 f += string(ch)
417 for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
418 ch, _ = pb.NextCh()
419 f += string(ch)
420 }
421 for (ch >= '0' && ch <= '9') || ch == '.' {
422 ch, _ = pb.NextCh()
423 f += string(ch)
424 }
425 switch ch {
426 case 'd', 'x', 'X', 'o':
427 ai, stk = stk.PopInt()
428 pb.PutString(fmt.Sprintf(f, ai))
429 case 's':
430 a, stk = stk.PopString()
431 pb.PutString(fmt.Sprintf(f, a))
432 case 'c':
433 ai, stk = stk.PopInt()
434 pb.PutString(fmt.Sprintf(f, ai))
435 }
436
437 case 'p':
438 ch, _ = pb.NextCh()
439 ai = int(ch - '1')
440 if ai >= 0 && ai < len(params) {
441 stk = stk.Push(params[ai])
442 } else {
443 stk = stk.Push(0)
444 }
445
446 case 'P':
447 ch, _ = pb.NextCh()
448 if ch >= 'A' && ch <= 'Z' {
449 svars[int(ch-'A')], stk = stk.PopString()
450 } else if ch >= 'a' && ch <= 'z' {
451 dvars[int(ch-'a')], stk = stk.PopString()
452 }
453
454 case 'g':
455 ch, _ = pb.NextCh()
456 if ch >= 'A' && ch <= 'Z' {
457 stk = stk.Push(svars[int(ch-'A')])
458 } else if ch >= 'a' && ch <= 'z' {
459 stk = stk.Push(dvars[int(ch-'a')])
460 }
461
462 case '\'':
463 ch, _ = pb.NextCh()
464 _, _ = pb.NextCh()
465 stk = stk.Push(int(ch))
466
467 case '{':
468 ai = 0
469 ch, _ = pb.NextCh()
470 for ch >= '0' && ch <= '9' {
471 ai *= 10
472 ai += int(ch - '0')
473 ch, _ = pb.NextCh()
474 }
475
476 stk = stk.Push(ai)
477
478 case 'l':
479 a, stk = stk.PopString()
480 stk = stk.Push(len(a))
481
482 case '+':
483 bi, stk = stk.PopInt()
484 ai, stk = stk.PopInt()
485 stk = stk.Push(ai + bi)
486
487 case '-':
488 bi, stk = stk.PopInt()
489 ai, stk = stk.PopInt()
490 stk = stk.Push(ai - bi)
491
492 case '*':
493 bi, stk = stk.PopInt()
494 ai, stk = stk.PopInt()
495 stk = stk.Push(ai * bi)
496
497 case '/':
498 bi, stk = stk.PopInt()
499 ai, stk = stk.PopInt()
500 if bi != 0 {
501 stk = stk.Push(ai / bi)
502 } else {
503 stk = stk.Push(0)
504 }
505
506 case 'm':
507 bi, stk = stk.PopInt()
508 ai, stk = stk.PopInt()
509 if bi != 0 {
510 stk = stk.Push(ai % bi)
511 } else {
512 stk = stk.Push(0)
513 }
514
515 case '&':
516 bi, stk = stk.PopInt()
517 ai, stk = stk.PopInt()
518 stk = stk.Push(ai & bi)
519
520 case '|':
521 bi, stk = stk.PopInt()
522 ai, stk = stk.PopInt()
523 stk = stk.Push(ai | bi)
524
525 case '^':
526 bi, stk = stk.PopInt()
527 ai, stk = stk.PopInt()
528 stk = stk.Push(ai ^ bi)
529
530 case '~':
531 ai, stk = stk.PopInt()
532 stk = stk.Push(ai ^ -1)
533
534 case '!':
535 ai, stk = stk.PopInt()
536 stk = stk.Push(ai == 0)
537
538 case '=':
539 bi, stk = stk.PopInt()
540 ai, stk = stk.PopInt()
541 stk = stk.Push(ai == bi)
542
543 case '>':
544 bi, stk = stk.PopInt()
545 ai, stk = stk.PopInt()
546 stk = stk.Push(ai > bi)
547
548 case '<':
549 bi, stk = stk.PopInt()
550 ai, stk = stk.PopInt()
551 stk = stk.Push(ai < bi)
552
553 case '?':
554
555 case ';':
556 skip = emit
557
558 case 't':
559 ai, stk = stk.PopInt()
560 if ai == 0 {
561 skip = toElse
562 }
563
564 case 'e':
565 skip = toEnd
566
567 default:
568 pb.PutString("%" + string(ch))
569 }
570 }
571
572 return pb.End()
573 }
574
575
576
577
578
579
580 func (t *Terminfo) TPuts(w io.Writer, s string) {
581 for {
582 beg := strings.Index(s, "$<")
583 if beg < 0 {
584
585 _, _ = io.WriteString(w, s)
586 return
587 }
588 _, _ = io.WriteString(w, s[:beg])
589 s = s[beg+2:]
590 end := strings.Index(s, ">")
591 if end < 0 {
592
593 _, _ = io.WriteString(w, "$<"+s)
594 return
595 }
596 val := s[:end]
597 s = s[end+1:]
598 padus := 0
599 unit := time.Millisecond
600 dot := false
601 loop:
602 for i := range val {
603 switch val[i] {
604 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
605 padus *= 10
606 padus += int(val[i] - '0')
607 if dot {
608 unit /= 10
609 }
610 case '.':
611 if !dot {
612 dot = true
613 } else {
614 break loop
615 }
616 default:
617 break loop
618 }
619 }
620
621
622
623
624 if len(t.PadChar) > 0 {
625 time.Sleep(unit * time.Duration(padus))
626 }
627 }
628 }
629
630
631
632 func (t *Terminfo) TGoto(col, row int) string {
633 return t.TParm(t.SetCursor, row, col)
634 }
635
636
637
638 func (t *Terminfo) TColor(fi, bi int) string {
639 rv := ""
640
641
642
643
644 if t.Colors == 8 {
645 if fi > 7 && fi < 16 {
646 fi -= 8
647 }
648 if bi > 7 && bi < 16 {
649 bi -= 8
650 }
651 }
652 if t.Colors > fi && fi >= 0 {
653 rv += t.TParm(t.SetFg, fi)
654 }
655 if t.Colors > bi && bi >= 0 {
656 rv += t.TParm(t.SetBg, bi)
657 }
658 return rv
659 }
660
661 var (
662 dblock sync.Mutex
663 terminfos = make(map[string]*Terminfo)
664 )
665
666
667 func AddTerminfo(t *Terminfo) {
668 dblock.Lock()
669 terminfos[t.Name] = t
670 for _, x := range t.Aliases {
671 terminfos[x] = t
672 }
673 dblock.Unlock()
674 }
675
676
677 func LookupTerminfo(name string) (*Terminfo, error) {
678 if name == "" {
679
680
681 return nil, ErrTermNotFound
682 }
683
684 addtruecolor := false
685 add256color := false
686 switch os.Getenv("COLORTERM") {
687 case "truecolor", "24bit", "24-bit":
688 addtruecolor = true
689 }
690 dblock.Lock()
691 t := terminfos[name]
692 dblock.Unlock()
693
694
695
696 if t != nil && t.TrueColor {
697 addtruecolor = true
698 } else if t == nil && strings.HasSuffix(name, "-truecolor") {
699
700 suffixes := []string{
701 "-256color",
702 "-88color",
703 "-color",
704 "",
705 }
706 base := name[:len(name)-len("-truecolor")]
707 for _, s := range suffixes {
708 if t, _ = LookupTerminfo(base + s); t != nil {
709 addtruecolor = true
710 break
711 }
712 }
713 }
714
715
716 if t == nil && strings.HasSuffix(name, "-256color") {
717 suffixes := []string{
718 "-88color",
719 "-color",
720 }
721 base := name[:len(name)-len("-256color")]
722 for _, s := range suffixes {
723 if t, _ = LookupTerminfo(base + s); t != nil {
724 add256color = true
725 break
726 }
727 }
728 }
729
730 if t == nil {
731 return nil, ErrTermNotFound
732 }
733
734 switch os.Getenv("TCELL_TRUECOLOR") {
735 case "":
736 case "disable":
737 addtruecolor = false
738 default:
739 addtruecolor = true
740 }
741
742
743
744
745 if addtruecolor &&
746 t.SetFgBgRGB == "" &&
747 t.SetFgRGB == "" &&
748 t.SetBgRGB == "" {
749
750
751 t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
752 t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
753 t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
754 "48;2;%p4%d;%p5%d;%p6%dm"
755 }
756
757 if add256color {
758 t.Colors = 256
759 t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
760 t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
761 t.SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m"
762 t.ResetFgBg = "\x1b[39;49m"
763 }
764 return t, nil
765 }
766
View as plain text