1// Copyright 2019 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	"compress/gzip"
20	"crypto/sha1"
21	"encoding/json"
22	"errors"
23	"fmt"
24	"io"
25	"os"
26	"path"
27	"path/filepath"
28	"strconv"
29	"strings"
30	"sync"
31)
32
33var (
34	// ErrTermNotFound indicates that a suitable terminal entry could
35	// not be found.  This can result from either not having TERM set,
36	// or from the TERM failing to support certain minimal functionality,
37	// in particular absolute cursor addressability (the cup capability)
38	// is required.  For example, legacy "adm3" lacks this capability,
39	// whereas the slightly newer "adm3a" supports it.  This failure
40	// occurs most often with "dumb".
41	ErrTermNotFound = errors.New("terminal entry not found")
42)
43
44// Terminfo represents a terminfo entry.  Note that we use friendly names
45// in Go, but when we write out JSON, we use the same names as terminfo.
46// The name, aliases and smous, rmous fields do not come from terminfo directly.
47type Terminfo struct {
48	Name         string   `json:"name"`
49	Aliases      []string `json:"aliases,omitempty"`
50	Columns      int      `json:"cols,omitempty"`   // cols
51	Lines        int      `json:"lines,omitempty"`  // lines
52	Colors       int      `json:"colors,omitempty"` // colors
53	Bell         string   `json:"bell,omitempty"`   // bell
54	Clear        string   `json:"clear,omitempty"`  // clear
55	EnterCA      string   `json:"smcup,omitempty"`  // smcup
56	ExitCA       string   `json:"rmcup,omitempty"`  // rmcup
57	ShowCursor   string   `json:"cnorm,omitempty"`  // cnorm
58	HideCursor   string   `json:"civis,omitempty"`  // civis
59	AttrOff      string   `json:"sgr0,omitempty"`   // sgr0
60	Underline    string   `json:"smul,omitempty"`   // smul
61	Bold         string   `json:"bold,omitempty"`   // bold
62	Blink        string   `json:"blink,omitempty"`  // blink
63	Reverse      string   `json:"rev,omitempty"`    // rev
64	Dim          string   `json:"dim,omitempty"`    // dim
65	EnterKeypad  string   `json:"smkx,omitempty"`   // smkx
66	ExitKeypad   string   `json:"rmkx,omitempty"`   // rmkx
67	SetFg        string   `json:"setaf,omitempty"`  // setaf
68	SetBg        string   `json:"setbg,omitempty"`  // setab
69	SetCursor    string   `json:"cup,omitempty"`    // cup
70	CursorBack1  string   `json:"cub1,omitempty"`   // cub1
71	CursorUp1    string   `json:"cuu1,omitempty"`   // cuu1
72	PadChar      string   `json:"pad,omitempty"`    // pad
73	KeyBackspace string   `json:"kbs,omitempty"`    // kbs
74	KeyF1        string   `json:"kf1,omitempty"`    // kf1
75	KeyF2        string   `json:"kf2,omitempty"`    // kf2
76	KeyF3        string   `json:"kf3,omitempty"`    // kf3
77	KeyF4        string   `json:"kf4,omitempty"`    // kf4
78	KeyF5        string   `json:"kf5,omitempty"`    // kf5
79	KeyF6        string   `json:"kf6,omitempty"`    // kf6
80	KeyF7        string   `json:"kf7,omitempty"`    // kf7
81	KeyF8        string   `json:"kf8,omitempty"`    // kf8
82	KeyF9        string   `json:"kf9,omitempty"`    // kf9
83	KeyF10       string   `json:"kf10,omitempty"`   // kf10
84	KeyF11       string   `json:"kf11,omitempty"`   // kf11
85	KeyF12       string   `json:"kf12,omitempty"`   // kf12
86	KeyF13       string   `json:"kf13,omitempty"`   // kf13
87	KeyF14       string   `json:"kf14,omitempty"`   // kf14
88	KeyF15       string   `json:"kf15,omitempty"`   // kf15
89	KeyF16       string   `json:"kf16,omitempty"`   // kf16
90	KeyF17       string   `json:"kf17,omitempty"`   // kf17
91	KeyF18       string   `json:"kf18,omitempty"`   // kf18
92	KeyF19       string   `json:"kf19,omitempty"`   // kf19
93	KeyF20       string   `json:"kf20,omitempty"`   // kf20
94	KeyF21       string   `json:"kf21,omitempty"`   // kf21
95	KeyF22       string   `json:"kf22,omitempty"`   // kf22
96	KeyF23       string   `json:"kf23,omitempty"`   // kf23
97	KeyF24       string   `json:"kf24,omitempty"`   // kf24
98	KeyF25       string   `json:"kf25,omitempty"`   // kf25
99	KeyF26       string   `json:"kf26,omitempty"`   // kf26
100	KeyF27       string   `json:"kf27,omitempty"`   // kf27
101	KeyF28       string   `json:"kf28,omitempty"`   // kf28
102	KeyF29       string   `json:"kf29,omitempty"`   // kf29
103	KeyF30       string   `json:"kf30,omitempty"`   // kf30
104	KeyF31       string   `json:"kf31,omitempty"`   // kf31
105	KeyF32       string   `json:"kf32,omitempty"`   // kf32
106	KeyF33       string   `json:"kf33,omitempty"`   // kf33
107	KeyF34       string   `json:"kf34,omitempty"`   // kf34
108	KeyF35       string   `json:"kf35,omitempty"`   // kf35
109	KeyF36       string   `json:"kf36,omitempty"`   // kf36
110	KeyF37       string   `json:"kf37,omitempty"`   // kf37
111	KeyF38       string   `json:"kf38,omitempty"`   // kf38
112	KeyF39       string   `json:"kf39,omitempty"`   // kf39
113	KeyF40       string   `json:"kf40,omitempty"`   // kf40
114	KeyF41       string   `json:"kf41,omitempty"`   // kf41
115	KeyF42       string   `json:"kf42,omitempty"`   // kf42
116	KeyF43       string   `json:"kf43,omitempty"`   // kf43
117	KeyF44       string   `json:"kf44,omitempty"`   // kf44
118	KeyF45       string   `json:"kf45,omitempty"`   // kf45
119	KeyF46       string   `json:"kf46,omitempty"`   // kf46
120	KeyF47       string   `json:"kf47,omitempty"`   // kf47
121	KeyF48       string   `json:"kf48,omitempty"`   // kf48
122	KeyF49       string   `json:"kf49,omitempty"`   // kf49
123	KeyF50       string   `json:"kf50,omitempty"`   // kf50
124	KeyF51       string   `json:"kf51,omitempty"`   // kf51
125	KeyF52       string   `json:"kf52,omitempty"`   // kf52
126	KeyF53       string   `json:"kf53,omitempty"`   // kf53
127	KeyF54       string   `json:"kf54,omitempty"`   // kf54
128	KeyF55       string   `json:"kf55,omitempty"`   // kf55
129	KeyF56       string   `json:"kf56,omitempty"`   // kf56
130	KeyF57       string   `json:"kf57,omitempty"`   // kf57
131	KeyF58       string   `json:"kf58,omitempty"`   // kf58
132	KeyF59       string   `json:"kf59,omitempty"`   // kf59
133	KeyF60       string   `json:"kf60,omitempty"`   // kf60
134	KeyF61       string   `json:"kf61,omitempty"`   // kf61
135	KeyF62       string   `json:"kf62,omitempty"`   // kf62
136	KeyF63       string   `json:"kf63,omitempty"`   // kf63
137	KeyF64       string   `json:"kf64,omitempty"`   // kf64
138	KeyInsert    string   `json:"kich,omitempty"`   // kich1
139	KeyDelete    string   `json:"kdch,omitempty"`   // kdch1
140	KeyHome      string   `json:"khome,omitempty"`  // khome
141	KeyEnd       string   `json:"kend,omitempty"`   // kend
142	KeyHelp      string   `json:"khlp,omitempty"`   // khlp
143	KeyPgUp      string   `json:"kpp,omitempty"`    // kpp
144	KeyPgDn      string   `json:"knp,omitempty"`    // knp
145	KeyUp        string   `json:"kcuu1,omitempty"`  // kcuu1
146	KeyDown      string   `json:"kcud1,omitempty"`  // kcud1
147	KeyLeft      string   `json:"kcub1,omitempty"`  // kcub1
148	KeyRight     string   `json:"kcuf1,omitempty"`  // kcuf1
149	KeyBacktab   string   `json:"kcbt,omitempty"`   // kcbt
150	KeyExit      string   `json:"kext,omitempty"`   // kext
151	KeyClear     string   `json:"kclr,omitempty"`   // kclr
152	KeyPrint     string   `json:"kprt,omitempty"`   // kprt
153	KeyCancel    string   `json:"kcan,omitempty"`   // kcan
154	Mouse        string   `json:"kmous,omitempty"`  // kmous
155	MouseMode    string   `json:"XM,omitempty"`     // XM
156	AltChars     string   `json:"acsc,omitempty"`   // acsc
157	EnterAcs     string   `json:"smacs,omitempty"`  // smacs
158	ExitAcs      string   `json:"rmacs,omitempty"`  // rmacs
159	EnableAcs    string   `json:"enacs,omitempty"`  // enacs
160	KeyShfRight  string   `json:"kRIT,omitempty"`   // kRIT
161	KeyShfLeft   string   `json:"kLFT,omitempty"`   // kLFT
162	KeyShfHome   string   `json:"kHOM,omitempty"`   // kHOM
163	KeyShfEnd    string   `json:"kEND,omitempty"`   // kEND
164
165	// These are non-standard extensions to terminfo.  This includes
166	// true color support, and some additional keys.  Its kind of bizarre
167	// that shifted variants of left and right exist, but not up and down.
168	// Terminal support for these are going to vary amongst XTerm
169	// emulations, so don't depend too much on them in your application.
170
171	SetFgBg         string `json:"_setfgbg,omitempty"`    // setfgbg
172	SetFgBgRGB      string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
173	SetFgRGB        string `json:"_setfrgb,omitempty"`    // setfrgb
174	SetBgRGB        string `json:"_setbrgb,omitempty"`    // setbrgb
175	KeyShfUp        string `json:"_kscu1,omitempty"`      // shift-up
176	KeyShfDown      string `json:"_kscud1,omitempty"`     // shift-down
177	KeyCtrlUp       string `json:"_kccu1,omitempty"`      // ctrl-up
178	KeyCtrlDown     string `json:"_kccud1,omitempty"`     // ctrl-left
179	KeyCtrlRight    string `json:"_kccuf1,omitempty"`     // ctrl-right
180	KeyCtrlLeft     string `json:"_kccub1,omitempty"`     // ctrl-left
181	KeyMetaUp       string `json:"_kmcu1,omitempty"`      // meta-up
182	KeyMetaDown     string `json:"_kmcud1,omitempty"`     // meta-left
183	KeyMetaRight    string `json:"_kmcuf1,omitempty"`     // meta-right
184	KeyMetaLeft     string `json:"_kmcub1,omitempty"`     // meta-left
185	KeyAltUp        string `json:"_kacu1,omitempty"`      // alt-up
186	KeyAltDown      string `json:"_kacud1,omitempty"`     // alt-left
187	KeyAltRight     string `json:"_kacuf1,omitempty"`     // alt-right
188	KeyAltLeft      string `json:"_kacub1,omitempty"`     // alt-left
189	KeyCtrlHome     string `json:"_kchome,omitempty"`
190	KeyCtrlEnd      string `json:"_kcend,omitempty"`
191	KeyMetaHome     string `json:"_kmhome,omitempty"`
192	KeyMetaEnd      string `json:"_kmend,omitempty"`
193	KeyAltHome      string `json:"_kahome,omitempty"`
194	KeyAltEnd       string `json:"_kaend,omitempty"`
195	KeyAltShfUp     string `json:"_kascu1,omitempty"`
196	KeyAltShfDown   string `json:"_kascud1,omitempty"`
197	KeyAltShfLeft   string `json:"_kascub1,omitempty"`
198	KeyAltShfRight  string `json:"_kascuf1,omitempty"`
199	KeyMetaShfUp    string `json:"_kmscu1,omitempty"`
200	KeyMetaShfDown  string `json:"_kmscud1,omitempty"`
201	KeyMetaShfLeft  string `json:"_kmscub1,omitempty"`
202	KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
203	KeyCtrlShfUp    string `json:"_kcscu1,omitempty"`
204	KeyCtrlShfDown  string `json:"_kcscud1,omitempty"`
205	KeyCtrlShfLeft  string `json:"_kcscub1,omitempty"`
206	KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
207	KeyCtrlShfHome  string `json:"_kcHOME,omitempty"`
208	KeyCtrlShfEnd   string `json:"_kcEND,omitempty"`
209	KeyAltShfHome   string `json:"_kaHOME,omitempty"`
210	KeyAltShfEnd    string `json:"_kaEND,omitempty"`
211	KeyMetaShfHome  string `json:"_kmHOME,omitempty"`
212	KeyMetaShfEnd   string `json:"_kmEND,omitempty"`
213}
214
215type stackElem struct {
216	s     string
217	i     int
218	isStr bool
219	isInt bool
220}
221
222type stack []stackElem
223
224func (st stack) Push(v string) stack {
225	e := stackElem{
226		s:     v,
227		isStr: true,
228	}
229	return append(st, e)
230}
231
232func (st stack) Pop() (string, stack) {
233	v := ""
234	if len(st) > 0 {
235		e := st[len(st)-1]
236		st = st[:len(st)-1]
237		if e.isStr {
238			v = e.s
239		} else {
240			v = strconv.Itoa(e.i)
241		}
242	}
243	return v, st
244}
245
246func (st stack) PopInt() (int, stack) {
247	if len(st) > 0 {
248		e := st[len(st)-1]
249		st = st[:len(st)-1]
250		if e.isInt {
251			return e.i, st
252		} else if e.isStr {
253			i, _ := strconv.Atoi(e.s)
254			return i, st
255		}
256	}
257	return 0, st
258}
259
260func (st stack) PopBool() (bool, stack) {
261	if len(st) > 0 {
262		e := st[len(st)-1]
263		st = st[:len(st)-1]
264		if e.isStr {
265			if e.s == "1" {
266				return true, st
267			}
268			return false, st
269		} else if e.i == 1 {
270			return true, st
271		} else {
272			return false, st
273		}
274	}
275	return false, st
276}
277
278func (st stack) PushInt(i int) stack {
279	e := stackElem{
280		i:     i,
281		isInt: true,
282	}
283	return append(st, e)
284}
285
286func (st stack) PushBool(i bool) stack {
287	if i {
288		return st.PushInt(1)
289	}
290	return st.PushInt(0)
291}
292
293func nextch(s string, index int) (byte, int) {
294	if index < len(s) {
295		return s[index], index + 1
296	}
297	return 0, index
298}
299
300// static vars
301var svars [26]string
302
303// paramsBuffer handles some persistent state for TParam.  Technically we
304// could probably dispense with this, but caching buffer arrays gives us
305// a nice little performance boost.  Furthermore, we know that TParam is
306// rarely (never?) called re-entrantly, so we can just reuse the same
307// buffers, making it thread-safe by stashing a lock.
308type paramsBuffer struct {
309	out bytes.Buffer
310	buf bytes.Buffer
311	lk  sync.Mutex
312}
313
314// Start initializes the params buffer with the initial string data.
315// It also locks the paramsBuffer.  The caller must call End() when
316// finished.
317func (pb *paramsBuffer) Start(s string) {
318	pb.lk.Lock()
319	pb.out.Reset()
320	pb.buf.Reset()
321	pb.buf.WriteString(s)
322}
323
324// End returns the final output from TParam, but it also releases the lock.
325func (pb *paramsBuffer) End() string {
326	s := pb.out.String()
327	pb.lk.Unlock()
328	return s
329}
330
331// NextCh returns the next input character to the expander.
332func (pb *paramsBuffer) NextCh() (byte, error) {
333	return pb.buf.ReadByte()
334}
335
336// PutCh "emits" (rather schedules for output) a single byte character.
337func (pb *paramsBuffer) PutCh(ch byte) {
338	pb.out.WriteByte(ch)
339}
340
341// PutString schedules a string for output.
342func (pb *paramsBuffer) PutString(s string) {
343	pb.out.WriteString(s)
344}
345
346var pb = &paramsBuffer{}
347
348// TParm takes a terminfo parameterized string, such as setaf or cup, and
349// evaluates the string, and returns the result with the parameter
350// applied.
351func (t *Terminfo) TParm(s string, p ...int) string {
352	var stk stack
353	var a, b string
354	var ai, bi int
355	var ab bool
356	var dvars [26]string
357	var params [9]int
358
359	pb.Start(s)
360
361	// make sure we always have 9 parameters -- makes it easier
362	// later to skip checks
363	for i := 0; i < len(params) && i < len(p); i++ {
364		params[i] = p[i]
365	}
366
367	nest := 0
368
369	for {
370
371		ch, err := pb.NextCh()
372		if err != nil {
373			break
374		}
375
376		if ch != '%' {
377			pb.PutCh(ch)
378			continue
379		}
380
381		ch, err = pb.NextCh()
382		if err != nil {
383			// XXX Error
384			break
385		}
386
387		switch ch {
388		case '%': // quoted %
389			pb.PutCh(ch)
390
391		case 'i': // increment both parameters (ANSI cup support)
392			params[0]++
393			params[1]++
394
395		case 'c', 's':
396			// NB: these, and 'd' below are special cased for
397			// efficiency.  They could be handled by the richer
398			// format support below, less efficiently.
399			a, stk = stk.Pop()
400			pb.PutString(a)
401
402		case 'd':
403			ai, stk = stk.PopInt()
404			pb.PutString(strconv.Itoa(ai))
405
406		case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
407			// This is pretty suboptimal, but this is rarely used.
408			// None of the mainstream terminals use any of this,
409			// and it would surprise me if this code is ever
410			// executed outside of test cases.
411			f := "%"
412			if ch == ':' {
413				ch, _ = pb.NextCh()
414			}
415			f += string(ch)
416			for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
417				ch, _ = pb.NextCh()
418				f += string(ch)
419			}
420			for (ch >= '0' && ch <= '9') || ch == '.' {
421				ch, _ = pb.NextCh()
422				f += string(ch)
423			}
424			switch ch {
425			case 'd', 'x', 'X', 'o':
426				ai, stk = stk.PopInt()
427				pb.PutString(fmt.Sprintf(f, ai))
428			case 'c', 's':
429				a, stk = stk.Pop()
430				pb.PutString(fmt.Sprintf(f, a))
431			}
432
433		case 'p': // push parameter
434			ch, _ = pb.NextCh()
435			ai = int(ch - '1')
436			if ai >= 0 && ai < len(params) {
437				stk = stk.PushInt(params[ai])
438			} else {
439				stk = stk.PushInt(0)
440			}
441
442		case 'P': // pop & store variable
443			ch, _ = pb.NextCh()
444			if ch >= 'A' && ch <= 'Z' {
445				svars[int(ch-'A')], stk = stk.Pop()
446			} else if ch >= 'a' && ch <= 'z' {
447				dvars[int(ch-'a')], stk = stk.Pop()
448			}
449
450		case 'g': // recall & push variable
451			ch, _ = pb.NextCh()
452			if ch >= 'A' && ch <= 'Z' {
453				stk = stk.Push(svars[int(ch-'A')])
454			} else if ch >= 'a' && ch <= 'z' {
455				stk = stk.Push(dvars[int(ch-'a')])
456			}
457
458		case '\'': // push(char)
459			ch, _ = pb.NextCh()
460			pb.NextCh() // must be ' but we don't check
461			stk = stk.Push(string(ch))
462
463		case '{': // push(int)
464			ai = 0
465			ch, _ = pb.NextCh()
466			for ch >= '0' && ch <= '9' {
467				ai *= 10
468				ai += int(ch - '0')
469				ch, _ = pb.NextCh()
470			}
471			// ch must be '}' but no verification
472			stk = stk.PushInt(ai)
473
474		case 'l': // push(strlen(pop))
475			a, stk = stk.Pop()
476			stk = stk.PushInt(len(a))
477
478		case '+':
479			bi, stk = stk.PopInt()
480			ai, stk = stk.PopInt()
481			stk = stk.PushInt(ai + bi)
482
483		case '-':
484			bi, stk = stk.PopInt()
485			ai, stk = stk.PopInt()
486			stk = stk.PushInt(ai - bi)
487
488		case '*':
489			bi, stk = stk.PopInt()
490			ai, stk = stk.PopInt()
491			stk = stk.PushInt(ai * bi)
492
493		case '/':
494			bi, stk = stk.PopInt()
495			ai, stk = stk.PopInt()
496			if bi != 0 {
497				stk = stk.PushInt(ai / bi)
498			} else {
499				stk = stk.PushInt(0)
500			}
501
502		case 'm': // push(pop mod pop)
503			bi, stk = stk.PopInt()
504			ai, stk = stk.PopInt()
505			if bi != 0 {
506				stk = stk.PushInt(ai % bi)
507			} else {
508				stk = stk.PushInt(0)
509			}
510
511		case '&': // AND
512			bi, stk = stk.PopInt()
513			ai, stk = stk.PopInt()
514			stk = stk.PushInt(ai & bi)
515
516		case '|': // OR
517			bi, stk = stk.PopInt()
518			ai, stk = stk.PopInt()
519			stk = stk.PushInt(ai | bi)
520
521		case '^': // XOR
522			bi, stk = stk.PopInt()
523			ai, stk = stk.PopInt()
524			stk = stk.PushInt(ai ^ bi)
525
526		case '~': // bit complement
527			ai, stk = stk.PopInt()
528			stk = stk.PushInt(ai ^ -1)
529
530		case '!': // logical NOT
531			ai, stk = stk.PopInt()
532			stk = stk.PushBool(ai != 0)
533
534		case '=': // numeric compare or string compare
535			b, stk = stk.Pop()
536			a, stk = stk.Pop()
537			stk = stk.PushBool(a == b)
538
539		case '>': // greater than, numeric
540			bi, stk = stk.PopInt()
541			ai, stk = stk.PopInt()
542			stk = stk.PushBool(ai > bi)
543
544		case '<': // less than, numeric
545			bi, stk = stk.PopInt()
546			ai, stk = stk.PopInt()
547			stk = stk.PushBool(ai < bi)
548
549		case '?': // start conditional
550
551		case 't':
552			ab, stk = stk.PopBool()
553			if ab {
554				// just keep going
555				break
556			}
557			nest = 0
558		ifloop:
559			// this loop consumes everything until we hit our else,
560			// or the end of the conditional
561			for {
562				ch, err = pb.NextCh()
563				if err != nil {
564					break
565				}
566				if ch != '%' {
567					continue
568				}
569				ch, _ = pb.NextCh()
570				switch ch {
571				case ';':
572					if nest == 0 {
573						break ifloop
574					}
575					nest--
576				case '?':
577					nest++
578				case 'e':
579					if nest == 0 {
580						break ifloop
581					}
582				}
583			}
584
585		case 'e':
586			// if we got here, it means we didn't use the else
587			// in the 't' case above, and we should skip until
588			// the end of the conditional
589			nest = 0
590		elloop:
591			for {
592				ch, err = pb.NextCh()
593				if err != nil {
594					break
595				}
596				if ch != '%' {
597					continue
598				}
599				ch, _ = pb.NextCh()
600				switch ch {
601				case ';':
602					if nest == 0 {
603						break elloop
604					}
605					nest--
606				case '?':
607					nest++
608				}
609			}
610
611		case ';': // endif
612
613		}
614	}
615
616	return pb.End()
617}
618
619// TPuts emits the string to the writer, but expands inline padding
620// indications (of the form $<[delay]> where [delay] is msec) to
621// a suitable number of padding characters (usually null bytes) based
622// upon the supplied baud.  At high baud rates, more padding characters
623// will be inserted.  All Terminfo based strings should be emitted using
624// this function.
625func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
626	for {
627		beg := strings.Index(s, "$<")
628		if beg < 0 {
629			// Most strings don't need padding, which is good news!
630			io.WriteString(w, s)
631			return
632		}
633		io.WriteString(w, s[:beg])
634		s = s[beg+2:]
635		end := strings.Index(s, ">")
636		if end < 0 {
637			// unterminated.. just emit bytes unadulterated
638			io.WriteString(w, "$<"+s)
639			return
640		}
641		val := s[:end]
642		s = s[end+1:]
643		padus := 0
644		unit := 1000
645		dot := false
646	loop:
647		for i := range val {
648			switch val[i] {
649			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
650				padus *= 10
651				padus += int(val[i] - '0')
652				if dot {
653					unit *= 10
654				}
655			case '.':
656				if !dot {
657					dot = true
658				} else {
659					break loop
660				}
661			default:
662				break loop
663			}
664		}
665		cnt := int(((baud / 8) * padus) / unit)
666		for cnt > 0 {
667			io.WriteString(w, t.PadChar)
668			cnt--
669		}
670	}
671}
672
673// TGoto returns a string suitable for addressing the cursor at the given
674// row and column.  The origin 0, 0 is in the upper left corner of the screen.
675func (t *Terminfo) TGoto(col, row int) string {
676	return t.TParm(t.SetCursor, row, col)
677}
678
679// TColor returns a string corresponding to the given foreground and background
680// colors.  Either fg or bg can be set to -1 to elide.
681func (t *Terminfo) TColor(fi, bi int) string {
682	rv := ""
683	// As a special case, we map bright colors to lower versions if the
684	// color table only holds 8.  For the remaining 240 colors, the user
685	// is out of luck.  Someday we could create a mapping table, but its
686	// not worth it.
687	if t.Colors == 8 {
688		if fi > 7 && fi < 16 {
689			fi -= 8
690		}
691		if bi > 7 && bi < 16 {
692			bi -= 8
693		}
694	}
695	if t.Colors > fi && fi >= 0 {
696		rv += t.TParm(t.SetFg, fi)
697	}
698	if t.Colors > bi && bi >= 0 {
699		rv += t.TParm(t.SetBg, bi)
700	}
701	return rv
702}
703
704var (
705	dblock    sync.Mutex
706	terminfos = make(map[string]*Terminfo)
707	aliases   = make(map[string]string)
708)
709
710// AddTerminfo can be called to register a new Terminfo entry.
711func AddTerminfo(t *Terminfo) {
712	dblock.Lock()
713	terminfos[t.Name] = t
714	for _, x := range t.Aliases {
715		terminfos[x] = t
716	}
717	dblock.Unlock()
718}
719
720func loadFromFile(fname string, term string) (*Terminfo, error) {
721	var e error
722	var f io.Reader
723	if f, e = os.Open(fname); e != nil {
724		return nil, e
725	}
726	if strings.HasSuffix(fname, ".gz") {
727		if f, e = gzip.NewReader(f); e != nil {
728			return nil, e
729		}
730	}
731	d := json.NewDecoder(f)
732	for {
733		t := &Terminfo{}
734		if e := d.Decode(t); e != nil {
735			if e == io.EOF {
736				return nil, ErrTermNotFound
737			}
738			return nil, e
739		}
740		if t.SetCursor == "" {
741			// This must be an alias record, return it.
742			return t, nil
743		}
744		if t.Name == term {
745			return t, nil
746		}
747		for _, a := range t.Aliases {
748			if a == term {
749				return t, nil
750			}
751		}
752	}
753}
754
755// LookupTerminfo attempts to find a definition for the named $TERM.
756// It first looks in the builtin database, which should cover just about
757// everyone.  If it can't find one there, then it will attempt to read
758// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb,
759// or as a database file.
760//
761// The database files are named by taking  terminal name, hashing it through
762// sha1, and then a subdirectory of the form database/hash[0:2]/hash[0:8]
763// (with an optional .gz extension).
764//
765// For other local database files, we will look for the database file using
766// the terminal name, so database/term[0:2]/term[0:8], again with optional
767// .gz extension.
768func LookupTerminfo(name string) (*Terminfo, error) {
769	if name == "" {
770		// else on windows: index out of bounds
771		// on the name[0] reference below
772		return nil, ErrTermNotFound
773	}
774
775	addtruecolor := false
776	switch os.Getenv("COLORTERM") {
777	case "truecolor", "24bit", "24-bit":
778		addtruecolor = true
779	}
780	dblock.Lock()
781	t := terminfos[name]
782	dblock.Unlock()
783
784	if t == nil {
785
786		var files []string
787		letter := fmt.Sprintf("%02x", name[0])
788		gzfile := path.Join(letter, name+".gz")
789		jsfile := path.Join(letter, name)
790		hash := fmt.Sprintf("%x", sha1.Sum([]byte(name)))
791		gzhfile := path.Join(hash[0:2], hash[0:8]+".gz")
792		jshfile := path.Join(hash[0:2], hash[0:8])
793
794		// Build up the search path.  Old versions of tcell used a
795		// single database file, whereas the new ones locate them
796		// in JSON (optionally compressed) files.
797		//
798		// The search path for "xterm" (SHA1 sig e2e28a8e...) looks
799		// like this:
800		//
801		// $TCELLDB/78/xterm.gz
802		// $TCELLDB/78/xterm
803		// $TCELLDB
804		// $HOME/.tcelldb/e2/e2e28a8e.gz
805		// $HOME/.tcelldb/e2/e2e28a8e
806		// $HOME/.tcelldb/78/xterm.gz
807		// $HOME/.tcelldb/78/xterm
808		// $HOME/.tcelldb
809		// $GOPATH/terminfo/database/e2/e2e28a8e.gz
810		// $GOPATH/terminfo/database/e2/e2e28a8e
811		// $GOPATH/terminfo/database/78/xterm.gz
812		// $GOPATH/terminfo/database/78/xterm
813		//
814		// Note that the legacy name lookups (78/xterm etc.) are
815		// provided for compatibility.  We do not actually deliver
816		// any files with this style of naming, to avoid collisions
817		// on case insensitive filesystems. (*cough* mac *cough*).
818
819		// If $GOPATH set, honor it, else assume $HOME/go just like
820		// modern golang does.
821		gopath := os.Getenv("GOPATH")
822		if gopath == "" {
823			gopath = path.Join(os.Getenv("HOME"), "go")
824		}
825		if pth := os.Getenv("TCELLDB"); pth != "" {
826			files = append(files,
827				path.Join(pth, gzfile),
828				path.Join(pth, jsfile),
829				pth)
830		}
831		if pth := os.Getenv("HOME"); pth != "" {
832			pth = path.Join(pth, ".tcelldb")
833			files = append(files,
834				path.Join(pth, gzhfile),
835				path.Join(pth, jshfile),
836				path.Join(pth, gzfile),
837				path.Join(pth, jsfile),
838				pth)
839		}
840
841		for _, pth := range filepath.SplitList(gopath) {
842			pth = path.Join(pth, "src", "github.com",
843				"gdamore", "tcell", "terminfo", "database")
844			files = append(files,
845				path.Join(pth, gzhfile),
846				path.Join(pth, jshfile),
847				path.Join(pth, gzfile),
848				path.Join(pth, jsfile))
849		}
850
851		for _, fname := range files {
852			t, _ = loadFromFile(fname, name)
853			if t != nil {
854				break
855			}
856		}
857		if t != nil {
858			if t.Name != name {
859				// Check for a database loop (no infinite
860				// recursion).
861				dblock.Lock()
862				if aliases[name] != "" {
863					dblock.Unlock()
864					return nil, ErrTermNotFound
865				}
866				aliases[name] = t.Name
867				dblock.Unlock()
868				return LookupTerminfo(t.Name)
869			}
870			dblock.Lock()
871			terminfos[name] = t
872			dblock.Unlock()
873		}
874	}
875
876	// If the name ends in -truecolor, then fabricate an entry
877	// from the corresponding -256color, -color, or bare terminal.
878	if t == nil && strings.HasSuffix(name, "-truecolor") {
879
880		suffixes := []string{
881			"-256color",
882			"-88color",
883			"-color",
884			"",
885		}
886		base := name[:len(name)-len("-truecolor")]
887		for _, s := range suffixes {
888			if t, _ = LookupTerminfo(base + s); t != nil {
889				addtruecolor = true
890				break
891			}
892		}
893	}
894
895	if t == nil {
896		return nil, ErrTermNotFound
897	}
898
899	switch os.Getenv("TCELL_TRUECOLOR") {
900	case "":
901	case "disable":
902		addtruecolor = false
903	default:
904		addtruecolor = true
905	}
906
907	// If the user has requested 24-bit color with $COLORTERM, then
908	// amend the value (unless already present).  This means we don't
909	// need to have a value present.
910	if addtruecolor &&
911		t.SetFgBgRGB == "" &&
912		t.SetFgRGB == "" &&
913		t.SetBgRGB == "" {
914
915		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
916		t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
917		t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
918		t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
919			"48;2;%p4%d;%p5%d;%p6%dm"
920	}
921
922	return t, nil
923}
924