1// Copyright 2021 The TCell Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use file except in compliance with the License.
5// You may obtain a copy of the license at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package terminfo
16
17import (
18	"bytes"
19	"errors"
20	"fmt"
21	"io"
22	"os"
23	"strconv"
24	"strings"
25	"sync"
26	"time"
27)
28
29var (
30	// ErrTermNotFound indicates that a suitable terminal entry could
31	// not be found.  This can result from either not having TERM set,
32	// or from the TERM failing to support certain minimal functionality,
33	// in particular absolute cursor addressability (the cup capability)
34	// is required.  For example, legacy "adm3" lacks this capability,
35	// whereas the slightly newer "adm3a" supports it.  This failure
36	// occurs most often with "dumb".
37	ErrTermNotFound = errors.New("terminal entry not found")
38)
39
40// Terminfo represents a terminfo entry.  Note that we use friendly names
41// in Go, but when we write out JSON, we use the same names as terminfo.
42// The name, aliases and smous, rmous fields do not come from terminfo directly.
43type Terminfo struct {
44	Name         string
45	Aliases      []string
46	Columns      int    // cols
47	Lines        int    // lines
48	Colors       int    // colors
49	Bell         string // bell
50	Clear        string // clear
51	EnterCA      string // smcup
52	ExitCA       string // rmcup
53	ShowCursor   string // cnorm
54	HideCursor   string // civis
55	AttrOff      string // sgr0
56	Underline    string // smul
57	Bold         string // bold
58	Blink        string // blink
59	Reverse      string // rev
60	Dim          string // dim
61	Italic       string // sitm
62	EnterKeypad  string // smkx
63	ExitKeypad   string // rmkx
64	SetFg        string // setaf
65	SetBg        string // setab
66	ResetFgBg    string // op
67	SetCursor    string // cup
68	CursorBack1  string // cub1
69	CursorUp1    string // cuu1
70	PadChar      string // pad
71	KeyBackspace string // kbs
72	KeyF1        string // kf1
73	KeyF2        string // kf2
74	KeyF3        string // kf3
75	KeyF4        string // kf4
76	KeyF5        string // kf5
77	KeyF6        string // kf6
78	KeyF7        string // kf7
79	KeyF8        string // kf8
80	KeyF9        string // kf9
81	KeyF10       string // kf10
82	KeyF11       string // kf11
83	KeyF12       string // kf12
84	KeyF13       string // kf13
85	KeyF14       string // kf14
86	KeyF15       string // kf15
87	KeyF16       string // kf16
88	KeyF17       string // kf17
89	KeyF18       string // kf18
90	KeyF19       string // kf19
91	KeyF20       string // kf20
92	KeyF21       string // kf21
93	KeyF22       string // kf22
94	KeyF23       string // kf23
95	KeyF24       string // kf24
96	KeyF25       string // kf25
97	KeyF26       string // kf26
98	KeyF27       string // kf27
99	KeyF28       string // kf28
100	KeyF29       string // kf29
101	KeyF30       string // kf30
102	KeyF31       string // kf31
103	KeyF32       string // kf32
104	KeyF33       string // kf33
105	KeyF34       string // kf34
106	KeyF35       string // kf35
107	KeyF36       string // kf36
108	KeyF37       string // kf37
109	KeyF38       string // kf38
110	KeyF39       string // kf39
111	KeyF40       string // kf40
112	KeyF41       string // kf41
113	KeyF42       string // kf42
114	KeyF43       string // kf43
115	KeyF44       string // kf44
116	KeyF45       string // kf45
117	KeyF46       string // kf46
118	KeyF47       string // kf47
119	KeyF48       string // kf48
120	KeyF49       string // kf49
121	KeyF50       string // kf50
122	KeyF51       string // kf51
123	KeyF52       string // kf52
124	KeyF53       string // kf53
125	KeyF54       string // kf54
126	KeyF55       string // kf55
127	KeyF56       string // kf56
128	KeyF57       string // kf57
129	KeyF58       string // kf58
130	KeyF59       string // kf59
131	KeyF60       string // kf60
132	KeyF61       string // kf61
133	KeyF62       string // kf62
134	KeyF63       string // kf63
135	KeyF64       string // kf64
136	KeyInsert    string // kich1
137	KeyDelete    string // kdch1
138	KeyHome      string // khome
139	KeyEnd       string // kend
140	KeyHelp      string // khlp
141	KeyPgUp      string // kpp
142	KeyPgDn      string // knp
143	KeyUp        string // kcuu1
144	KeyDown      string // kcud1
145	KeyLeft      string // kcub1
146	KeyRight     string // kcuf1
147	KeyBacktab   string // kcbt
148	KeyExit      string // kext
149	KeyClear     string // kclr
150	KeyPrint     string // kprt
151	KeyCancel    string // kcan
152	Mouse        string // kmous
153	AltChars     string // acsc
154	EnterAcs     string // smacs
155	ExitAcs      string // rmacs
156	EnableAcs    string // enacs
157	KeyShfRight  string // kRIT
158	KeyShfLeft   string // kLFT
159	KeyShfHome   string // kHOM
160	KeyShfEnd    string // kEND
161	KeyShfInsert string // kIC
162	KeyShfDelete string // kDC
163
164	// These are non-standard extensions to terminfo.  This includes
165	// true color support, and some additional keys.  Its kind of bizarre
166	// that shifted variants of left and right exist, but not up and down.
167	// Terminal support for these are going to vary amongst XTerm
168	// emulations, so don't depend too much on them in your application.
169
170	StrikeThrough   string // smxx
171	SetFgBg         string // setfgbg
172	SetFgBgRGB      string // setfgbgrgb
173	SetFgRGB        string // setfrgb
174	SetBgRGB        string // setbrgb
175	KeyShfUp        string // shift-up
176	KeyShfDown      string // shift-down
177	KeyShfPgUp      string // shift-kpp
178	KeyShfPgDn      string // shift-knp
179	KeyCtrlUp       string // ctrl-up
180	KeyCtrlDown     string // ctrl-left
181	KeyCtrlRight    string // ctrl-right
182	KeyCtrlLeft     string // ctrl-left
183	KeyMetaUp       string // meta-up
184	KeyMetaDown     string // meta-left
185	KeyMetaRight    string // meta-right
186	KeyMetaLeft     string // meta-left
187	KeyAltUp        string // alt-up
188	KeyAltDown      string // alt-left
189	KeyAltRight     string // alt-right
190	KeyAltLeft      string // alt-left
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 // bracketed paste mode
216	DisablePaste    string
217	PasteStart      string
218	PasteEnd        string
219	Modifiers       int
220	InsertChar      string // string to insert a character (ich1)
221	AutoMargin      bool   // true if writing to last cell in line advances
222	TrueColor       bool   // true if the terminal supports direct color
223}
224
225const (
226	ModifiersNone  = 0
227	ModifiersXTerm = 1
228)
229
230type stackElem struct {
231	s     string
232	i     int
233	isStr bool
234	isInt bool
235}
236
237type stack []stackElem
238
239func (st stack) Push(v string) stack {
240	e := stackElem{
241		s:     v,
242		isStr: true,
243	}
244	return append(st, e)
245}
246
247func (st stack) Pop() (string, stack) {
248	v := ""
249	if len(st) > 0 {
250		e := st[len(st)-1]
251		st = st[:len(st)-1]
252		if e.isStr {
253			v = e.s
254		} else {
255			v = strconv.Itoa(e.i)
256		}
257	}
258	return v, st
259}
260
261func (st stack) PopInt() (int, stack) {
262	if len(st) > 0 {
263		e := st[len(st)-1]
264		st = st[:len(st)-1]
265		if e.isInt {
266			return e.i, st
267		} else if e.isStr {
268			// If the string that was pushed was the representation
269			// of a number e.g. '123', then return the number. If the
270			// conversion doesn't work, assume the string pushed was
271			// intended to return, as an int, the ascii representation
272			// of the (one and only) character.
273			i, err := strconv.Atoi(e.s)
274			if err == nil {
275				return i, st
276			} else if len(e.s) >= 1 {
277				return int(e.s[0]), st
278			}
279		}
280	}
281	return 0, st
282}
283
284func (st stack) PopBool() (bool, stack) {
285	if len(st) > 0 {
286		e := st[len(st)-1]
287		st = st[:len(st)-1]
288		if e.isStr {
289			if e.s == "1" {
290				return true, st
291			}
292			return false, st
293		} else if e.i == 1 {
294			return true, st
295		} else {
296			return false, st
297		}
298	}
299	return false, st
300}
301
302func (st stack) PushInt(i int) stack {
303	e := stackElem{
304		i:     i,
305		isInt: true,
306	}
307	return append(st, e)
308}
309
310func (st stack) PushBool(i bool) stack {
311	if i {
312		return st.PushInt(1)
313	}
314	return st.PushInt(0)
315}
316
317// static vars
318var svars [26]string
319
320// paramsBuffer handles some persistent state for TParam.  Technically we
321// could probably dispense with this, but caching buffer arrays gives us
322// a nice little performance boost.  Furthermore, we know that TParam is
323// rarely (never?) called re-entrantly, so we can just reuse the same
324// buffers, making it thread-safe by stashing a lock.
325type paramsBuffer struct {
326	out bytes.Buffer
327	buf bytes.Buffer
328	lk  sync.Mutex
329}
330
331// Start initializes the params buffer with the initial string data.
332// It also locks the paramsBuffer.  The caller must call End() when
333// finished.
334func (pb *paramsBuffer) Start(s string) {
335	pb.lk.Lock()
336	pb.out.Reset()
337	pb.buf.Reset()
338	pb.buf.WriteString(s)
339}
340
341// End returns the final output from TParam, but it also releases the lock.
342func (pb *paramsBuffer) End() string {
343	s := pb.out.String()
344	pb.lk.Unlock()
345	return s
346}
347
348// NextCh returns the next input character to the expander.
349func (pb *paramsBuffer) NextCh() (byte, error) {
350	return pb.buf.ReadByte()
351}
352
353// PutCh "emits" (rather schedules for output) a single byte character.
354func (pb *paramsBuffer) PutCh(ch byte) {
355	pb.out.WriteByte(ch)
356}
357
358// PutString schedules a string for output.
359func (pb *paramsBuffer) PutString(s string) {
360	pb.out.WriteString(s)
361}
362
363var pb = &paramsBuffer{}
364
365// TParm takes a terminfo parameterized string, such as setaf or cup, and
366// evaluates the string, and returns the result with the parameter
367// applied.
368func (t *Terminfo) TParm(s string, p ...int) string {
369	var stk stack
370	var a, b string
371	var ai, bi int
372	var ab bool
373	var dvars [26]string
374	var params [9]int
375
376	pb.Start(s)
377
378	// make sure we always have 9 parameters -- makes it easier
379	// later to skip checks
380	for i := 0; i < len(params) && i < len(p); i++ {
381		params[i] = p[i]
382	}
383
384	nest := 0
385
386	for {
387
388		ch, err := pb.NextCh()
389		if err != nil {
390			break
391		}
392
393		if ch != '%' {
394			pb.PutCh(ch)
395			continue
396		}
397
398		ch, err = pb.NextCh()
399		if err != nil {
400			// XXX Error
401			break
402		}
403
404		switch ch {
405		case '%': // quoted %
406			pb.PutCh(ch)
407
408		case 'i': // increment both parameters (ANSI cup support)
409			params[0]++
410			params[1]++
411
412		case 'c', 's':
413			// NB: these, and 'd' below are special cased for
414			// efficiency.  They could be handled by the richer
415			// format support below, less efficiently.
416			a, stk = stk.Pop()
417			pb.PutString(a)
418
419		case 'd':
420			ai, stk = stk.PopInt()
421			pb.PutString(strconv.Itoa(ai))
422
423		case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
424			// This is pretty suboptimal, but this is rarely used.
425			// None of the mainstream terminals use any of this,
426			// and it would surprise me if this code is ever
427			// executed outside of test cases.
428			f := "%"
429			if ch == ':' {
430				ch, _ = pb.NextCh()
431			}
432			f += string(ch)
433			for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
434				ch, _ = pb.NextCh()
435				f += string(ch)
436			}
437			for (ch >= '0' && ch <= '9') || ch == '.' {
438				ch, _ = pb.NextCh()
439				f += string(ch)
440			}
441			switch ch {
442			case 'd', 'x', 'X', 'o':
443				ai, stk = stk.PopInt()
444				pb.PutString(fmt.Sprintf(f, ai))
445			case 'c', 's':
446				a, stk = stk.Pop()
447				pb.PutString(fmt.Sprintf(f, a))
448			}
449
450		case 'p': // push parameter
451			ch, _ = pb.NextCh()
452			ai = int(ch - '1')
453			if ai >= 0 && ai < len(params) {
454				stk = stk.PushInt(params[ai])
455			} else {
456				stk = stk.PushInt(0)
457			}
458
459		case 'P': // pop & store variable
460			ch, _ = pb.NextCh()
461			if ch >= 'A' && ch <= 'Z' {
462				svars[int(ch-'A')], stk = stk.Pop()
463			} else if ch >= 'a' && ch <= 'z' {
464				dvars[int(ch-'a')], stk = stk.Pop()
465			}
466
467		case 'g': // recall & push variable
468			ch, _ = pb.NextCh()
469			if ch >= 'A' && ch <= 'Z' {
470				stk = stk.Push(svars[int(ch-'A')])
471			} else if ch >= 'a' && ch <= 'z' {
472				stk = stk.Push(dvars[int(ch-'a')])
473			}
474
475		case '\'': // push(char)
476			ch, _ = pb.NextCh()
477			pb.NextCh() // must be ' but we don't check
478			stk = stk.Push(string(ch))
479
480		case '{': // push(int)
481			ai = 0
482			ch, _ = pb.NextCh()
483			for ch >= '0' && ch <= '9' {
484				ai *= 10
485				ai += int(ch - '0')
486				ch, _ = pb.NextCh()
487			}
488			// ch must be '}' but no verification
489			stk = stk.PushInt(ai)
490
491		case 'l': // push(strlen(pop))
492			a, stk = stk.Pop()
493			stk = stk.PushInt(len(a))
494
495		case '+':
496			bi, stk = stk.PopInt()
497			ai, stk = stk.PopInt()
498			stk = stk.PushInt(ai + bi)
499
500		case '-':
501			bi, stk = stk.PopInt()
502			ai, stk = stk.PopInt()
503			stk = stk.PushInt(ai - bi)
504
505		case '*':
506			bi, stk = stk.PopInt()
507			ai, stk = stk.PopInt()
508			stk = stk.PushInt(ai * bi)
509
510		case '/':
511			bi, stk = stk.PopInt()
512			ai, stk = stk.PopInt()
513			if bi != 0 {
514				stk = stk.PushInt(ai / bi)
515			} else {
516				stk = stk.PushInt(0)
517			}
518
519		case 'm': // push(pop mod pop)
520			bi, stk = stk.PopInt()
521			ai, stk = stk.PopInt()
522			if bi != 0 {
523				stk = stk.PushInt(ai % bi)
524			} else {
525				stk = stk.PushInt(0)
526			}
527
528		case '&': // AND
529			bi, stk = stk.PopInt()
530			ai, stk = stk.PopInt()
531			stk = stk.PushInt(ai & bi)
532
533		case '|': // OR
534			bi, stk = stk.PopInt()
535			ai, stk = stk.PopInt()
536			stk = stk.PushInt(ai | bi)
537
538		case '^': // XOR
539			bi, stk = stk.PopInt()
540			ai, stk = stk.PopInt()
541			stk = stk.PushInt(ai ^ bi)
542
543		case '~': // bit complement
544			ai, stk = stk.PopInt()
545			stk = stk.PushInt(ai ^ -1)
546
547		case '!': // logical NOT
548			ai, stk = stk.PopInt()
549			stk = stk.PushBool(ai != 0)
550
551		case '=': // numeric compare or string compare
552			b, stk = stk.Pop()
553			a, stk = stk.Pop()
554			stk = stk.PushBool(a == b)
555
556		case '>': // greater than, numeric
557			bi, stk = stk.PopInt()
558			ai, stk = stk.PopInt()
559			stk = stk.PushBool(ai > bi)
560
561		case '<': // less than, numeric
562			bi, stk = stk.PopInt()
563			ai, stk = stk.PopInt()
564			stk = stk.PushBool(ai < bi)
565
566		case '?': // start conditional
567
568		case 't':
569			ab, stk = stk.PopBool()
570			if ab {
571				// just keep going
572				break
573			}
574			nest = 0
575		ifloop:
576			// this loop consumes everything until we hit our else,
577			// or the end of the conditional
578			for {
579				ch, err = pb.NextCh()
580				if err != nil {
581					break
582				}
583				if ch != '%' {
584					continue
585				}
586				ch, _ = pb.NextCh()
587				switch ch {
588				case ';':
589					if nest == 0 {
590						break ifloop
591					}
592					nest--
593				case '?':
594					nest++
595				case 'e':
596					if nest == 0 {
597						break ifloop
598					}
599				}
600			}
601
602		case 'e':
603			// if we got here, it means we didn't use the else
604			// in the 't' case above, and we should skip until
605			// the end of the conditional
606			nest = 0
607		elloop:
608			for {
609				ch, err = pb.NextCh()
610				if err != nil {
611					break
612				}
613				if ch != '%' {
614					continue
615				}
616				ch, _ = pb.NextCh()
617				switch ch {
618				case ';':
619					if nest == 0 {
620						break elloop
621					}
622					nest--
623				case '?':
624					nest++
625				}
626			}
627
628		case ';': // endif
629
630		}
631	}
632
633	return pb.End()
634}
635
636// TPuts emits the string to the writer, but expands inline padding
637// indications (of the form $<[delay]> where [delay] is msec) to
638// a suitable time (unless the terminfo string indicates this isn't needed
639// by specifying npc - no padding).  All Terminfo based strings should be
640// emitted using this function.
641func (t *Terminfo) TPuts(w io.Writer, s string) {
642	for {
643		beg := strings.Index(s, "$<")
644		if beg < 0 {
645			// Most strings don't need padding, which is good news!
646			io.WriteString(w, s)
647			return
648		}
649		io.WriteString(w, s[:beg])
650		s = s[beg+2:]
651		end := strings.Index(s, ">")
652		if end < 0 {
653			// unterminated.. just emit bytes unadulterated
654			io.WriteString(w, "$<"+s)
655			return
656		}
657		val := s[:end]
658		s = s[end+1:]
659		padus := 0
660		unit := time.Millisecond
661		dot := false
662	loop:
663		for i := range val {
664			switch val[i] {
665			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
666				padus *= 10
667				padus += int(val[i] - '0')
668				if dot {
669					unit /= 10
670				}
671			case '.':
672				if !dot {
673					dot = true
674				} else {
675					break loop
676				}
677			default:
678				break loop
679			}
680		}
681
682		// Curses historically uses padding to achieve "fine grained"
683		// delays. We have much better clocks these days, and so we
684		// do not rely on padding but simply sleep a bit.
685		if len(t.PadChar) > 0 {
686			time.Sleep(unit * time.Duration(padus))
687		}
688	}
689}
690
691// TGoto returns a string suitable for addressing the cursor at the given
692// row and column.  The origin 0, 0 is in the upper left corner of the screen.
693func (t *Terminfo) TGoto(col, row int) string {
694	return t.TParm(t.SetCursor, row, col)
695}
696
697// TColor returns a string corresponding to the given foreground and background
698// colors.  Either fg or bg can be set to -1 to elide.
699func (t *Terminfo) TColor(fi, bi int) string {
700	rv := ""
701	// As a special case, we map bright colors to lower versions if the
702	// color table only holds 8.  For the remaining 240 colors, the user
703	// is out of luck.  Someday we could create a mapping table, but its
704	// not worth it.
705	if t.Colors == 8 {
706		if fi > 7 && fi < 16 {
707			fi -= 8
708		}
709		if bi > 7 && bi < 16 {
710			bi -= 8
711		}
712	}
713	if t.Colors > fi && fi >= 0 {
714		rv += t.TParm(t.SetFg, fi)
715	}
716	if t.Colors > bi && bi >= 0 {
717		rv += t.TParm(t.SetBg, bi)
718	}
719	return rv
720}
721
722var (
723	dblock    sync.Mutex
724	terminfos = make(map[string]*Terminfo)
725	aliases   = make(map[string]string)
726)
727
728// AddTerminfo can be called to register a new Terminfo entry.
729func AddTerminfo(t *Terminfo) {
730	dblock.Lock()
731	terminfos[t.Name] = t
732	for _, x := range t.Aliases {
733		terminfos[x] = t
734	}
735	dblock.Unlock()
736}
737
738// LookupTerminfo attempts to find a definition for the named $TERM.
739func LookupTerminfo(name string) (*Terminfo, error) {
740	if name == "" {
741		// else on windows: index out of bounds
742		// on the name[0] reference below
743		return nil, ErrTermNotFound
744	}
745
746	addtruecolor := false
747	add256color := false
748	switch os.Getenv("COLORTERM") {
749	case "truecolor", "24bit", "24-bit":
750		addtruecolor = true
751	}
752	dblock.Lock()
753	t := terminfos[name]
754	dblock.Unlock()
755
756	// If the name ends in -truecolor, then fabricate an entry
757	// from the corresponding -256color, -color, or bare terminal.
758	if t != nil && t.TrueColor {
759		addtruecolor = true
760	} else if t == nil && strings.HasSuffix(name, "-truecolor") {
761
762		suffixes := []string{
763			"-256color",
764			"-88color",
765			"-color",
766			"",
767		}
768		base := name[:len(name)-len("-truecolor")]
769		for _, s := range suffixes {
770			if t, _ = LookupTerminfo(base + s); t != nil {
771				addtruecolor = true
772				break
773			}
774		}
775	}
776
777	// If the name ends in -256color, maybe fabricate using the xterm 256 color sequences
778	if t == nil && strings.HasSuffix(name, "-256color") {
779		suffixes := []string{
780			"-88color",
781			"-color",
782		}
783		base := name[:len(name)-len("-256color")]
784		for _, s := range suffixes {
785			if t, _ = LookupTerminfo(base + s); t != nil {
786				add256color = true
787				break
788			}
789		}
790	}
791
792	if t == nil {
793		return nil, ErrTermNotFound
794	}
795
796	switch os.Getenv("TCELL_TRUECOLOR") {
797	case "":
798	case "disable":
799		addtruecolor = false
800	default:
801		addtruecolor = true
802	}
803
804	// If the user has requested 24-bit color with $COLORTERM, then
805	// amend the value (unless already present).  This means we don't
806	// need to have a value present.
807	if addtruecolor &&
808		t.SetFgBgRGB == "" &&
809		t.SetFgRGB == "" &&
810		t.SetBgRGB == "" {
811
812		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
813		t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
814		t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
815		t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
816			"48;2;%p4%d;%p5%d;%p6%dm"
817	}
818
819	if add256color {
820		t.Colors = 256
821		t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
822		t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
823		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"
824		t.ResetFgBg = "\x1b[39;49m"
825	}
826	return t, nil
827}
828