nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

terminfo.go (20110B)


      1 // Copyright 2024 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 
     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 	// 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.
     43 type 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 	CursorDefault           string
    224 	CursorBlinkingBlock     string
    225 	CursorSteadyBlock       string
    226 	CursorBlinkingUnderline string
    227 	CursorSteadyUnderline   string
    228 	CursorBlinkingBar       string
    229 	CursorSteadyBar         string
    230 	CursorColor             string // nothing uses it yet
    231 	CursorColorRGB          string // Cs (but not really because Cs uses X11 color string)
    232 	CursorColorReset        string // Cr
    233 	EnterUrl                string
    234 	ExitUrl                 string
    235 	SetWindowSize           string
    236 	SetWindowTitle          string // no terminfo extension
    237 	EnableFocusReporting    string
    238 	DisableFocusReporting   string
    239 	DisableAutoMargin       string // smam
    240 	EnableAutoMargin        string // rmam
    241 	DoubleUnderline         string // Smulx with param 2
    242 	CurlyUnderline          string // Smulx with param 3
    243 	DottedUnderline         string // Smulx with param 4
    244 	DashedUnderline         string // Smulx with param 5
    245 	UnderlineColor          string // Setuc1
    246 	UnderlineColorRGB       string // Setulc
    247 	UnderlineColorReset     string // ol
    248 	XTermLike               bool   // (XT) has XTerm extensions
    249 }
    250 
    251 const (
    252 	ModifiersNone  = 0
    253 	ModifiersXTerm = 1
    254 )
    255 
    256 type stack []interface{}
    257 
    258 func (st stack) Push(v interface{}) stack {
    259 	if b, ok := v.(bool); ok {
    260 		if b {
    261 			return append(st, 1)
    262 		} else {
    263 			return append(st, 0)
    264 		}
    265 	}
    266 	return append(st, v)
    267 }
    268 
    269 func (st stack) PopString() (string, stack) {
    270 	if len(st) > 0 {
    271 		e := st[len(st)-1]
    272 		var s string
    273 		switch v := e.(type) {
    274 		case int:
    275 			s = strconv.Itoa(v)
    276 		case string:
    277 			s = v
    278 		}
    279 		return s, st[:len(st)-1]
    280 	}
    281 	return "", st
    282 
    283 }
    284 func (st stack) PopInt() (int, stack) {
    285 	if len(st) > 0 {
    286 		e := st[len(st)-1]
    287 		var i int
    288 		switch v := e.(type) {
    289 		case int:
    290 			i = v
    291 		case string:
    292 			i, _ = strconv.Atoi(v)
    293 		}
    294 		return i, st[:len(st)-1]
    295 	}
    296 	return 0, st
    297 }
    298 
    299 // static vars
    300 var svars [26]string
    301 
    302 type paramsBuffer struct {
    303 	out bytes.Buffer
    304 	buf bytes.Buffer
    305 }
    306 
    307 // Start initializes the params buffer with the initial string data.
    308 // It also locks the paramsBuffer.  The caller must call End() when
    309 // finished.
    310 func (pb *paramsBuffer) Start(s string) {
    311 	pb.out.Reset()
    312 	pb.buf.Reset()
    313 	pb.buf.WriteString(s)
    314 }
    315 
    316 // End returns the final output from TParam, but it also releases the lock.
    317 func (pb *paramsBuffer) End() string {
    318 	s := pb.out.String()
    319 	return s
    320 }
    321 
    322 // NextCh returns the next input character to the expander.
    323 func (pb *paramsBuffer) NextCh() (byte, error) {
    324 	return pb.buf.ReadByte()
    325 }
    326 
    327 // PutCh "emits" (rather schedules for output) a single byte character.
    328 func (pb *paramsBuffer) PutCh(ch byte) {
    329 	pb.out.WriteByte(ch)
    330 }
    331 
    332 // PutString schedules a string for output.
    333 func (pb *paramsBuffer) PutString(s string) {
    334 	pb.out.WriteString(s)
    335 }
    336 
    337 // TParm takes a terminfo parameterized string, such as setaf or cup, and
    338 // evaluates the string, and returns the result with the parameter
    339 // applied.
    340 func (t *Terminfo) TParm(s string, p ...interface{}) string {
    341 	var stk stack
    342 	var a string
    343 	var ai, bi int
    344 	var dvars [26]string
    345 	var params [9]interface{}
    346 	var pb = &paramsBuffer{}
    347 
    348 	pb.Start(s)
    349 
    350 	// make sure we always have 9 parameters -- makes it easier
    351 	// later to skip checks
    352 	for i := 0; i < len(params) && i < len(p); i++ {
    353 		params[i] = p[i]
    354 	}
    355 
    356 	const (
    357 		emit = iota
    358 		toEnd
    359 		toElse
    360 	)
    361 
    362 	skip := emit
    363 
    364 	for {
    365 
    366 		ch, err := pb.NextCh()
    367 		if err != nil {
    368 			break
    369 		}
    370 
    371 		if ch != '%' {
    372 			if skip == emit {
    373 				pb.PutCh(ch)
    374 			}
    375 			continue
    376 		}
    377 
    378 		ch, err = pb.NextCh()
    379 		if err != nil {
    380 			// XXX Error
    381 			break
    382 		}
    383 		if skip == toEnd {
    384 			if ch == ';' {
    385 				skip = emit
    386 			}
    387 			continue
    388 		} else if skip == toElse {
    389 			if ch == 'e' || ch == ';' {
    390 				skip = emit
    391 			}
    392 			continue
    393 		}
    394 
    395 		switch ch {
    396 		case '%': // quoted %
    397 			pb.PutCh(ch)
    398 
    399 		case 'i': // increment both parameters (ANSI cup support)
    400 			if i, ok := params[0].(int); ok {
    401 				params[0] = i + 1
    402 			}
    403 			if i, ok := params[1].(int); ok {
    404 				params[1] = i + 1
    405 			}
    406 
    407 		case 's':
    408 			// NB: 's', 'c', and 'd' below are special cased for
    409 			// efficiency.  They could be handled by the richer
    410 			// format support below, less efficiently.
    411 			a, stk = stk.PopString()
    412 			pb.PutString(a)
    413 
    414 		case 'c':
    415 			// Integer as special character.
    416 			ai, stk = stk.PopInt()
    417 			pb.PutCh(byte(ai))
    418 
    419 		case 'd':
    420 			ai, stk = stk.PopInt()
    421 			pb.PutString(strconv.Itoa(ai))
    422 
    423 		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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 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 's':
    446 				a, stk = stk.PopString()
    447 				pb.PutString(fmt.Sprintf(f, a))
    448 			case 'c':
    449 				ai, stk = stk.PopInt()
    450 				pb.PutString(fmt.Sprintf(f, ai))
    451 			}
    452 
    453 		case 'p': // push parameter
    454 			ch, _ = pb.NextCh()
    455 			ai = int(ch - '1')
    456 			if ai >= 0 && ai < len(params) {
    457 				stk = stk.Push(params[ai])
    458 			} else {
    459 				stk = stk.Push(0)
    460 			}
    461 
    462 		case 'P': // pop & store variable
    463 			ch, _ = pb.NextCh()
    464 			if ch >= 'A' && ch <= 'Z' {
    465 				svars[int(ch-'A')], stk = stk.PopString()
    466 			} else if ch >= 'a' && ch <= 'z' {
    467 				dvars[int(ch-'a')], stk = stk.PopString()
    468 			}
    469 
    470 		case 'g': // recall & push variable
    471 			ch, _ = pb.NextCh()
    472 			if ch >= 'A' && ch <= 'Z' {
    473 				stk = stk.Push(svars[int(ch-'A')])
    474 			} else if ch >= 'a' && ch <= 'z' {
    475 				stk = stk.Push(dvars[int(ch-'a')])
    476 			}
    477 
    478 		case '\'': // push(char) - the integer value of it
    479 			ch, _ = pb.NextCh()
    480 			_, _ = pb.NextCh() // must be ' but we don't check
    481 			stk = stk.Push(int(ch))
    482 
    483 		case '{': // push(int)
    484 			ai = 0
    485 			ch, _ = pb.NextCh()
    486 			for ch >= '0' && ch <= '9' {
    487 				ai *= 10
    488 				ai += int(ch - '0')
    489 				ch, _ = pb.NextCh()
    490 			}
    491 			// ch must be '}' but no verification
    492 			stk = stk.Push(ai)
    493 
    494 		case 'l': // push(strlen(pop))
    495 			a, stk = stk.PopString()
    496 			stk = stk.Push(len(a))
    497 
    498 		case '+':
    499 			bi, stk = stk.PopInt()
    500 			ai, stk = stk.PopInt()
    501 			stk = stk.Push(ai + bi)
    502 
    503 		case '-':
    504 			bi, stk = stk.PopInt()
    505 			ai, stk = stk.PopInt()
    506 			stk = stk.Push(ai - bi)
    507 
    508 		case '*':
    509 			bi, stk = stk.PopInt()
    510 			ai, stk = stk.PopInt()
    511 			stk = stk.Push(ai * bi)
    512 
    513 		case '/':
    514 			bi, stk = stk.PopInt()
    515 			ai, stk = stk.PopInt()
    516 			if bi != 0 {
    517 				stk = stk.Push(ai / bi)
    518 			} else {
    519 				stk = stk.Push(0)
    520 			}
    521 
    522 		case 'm': // push(pop mod pop)
    523 			bi, stk = stk.PopInt()
    524 			ai, stk = stk.PopInt()
    525 			if bi != 0 {
    526 				stk = stk.Push(ai % bi)
    527 			} else {
    528 				stk = stk.Push(0)
    529 			}
    530 
    531 		case '&': // AND
    532 			bi, stk = stk.PopInt()
    533 			ai, stk = stk.PopInt()
    534 			stk = stk.Push(ai & bi)
    535 
    536 		case '|': // OR
    537 			bi, stk = stk.PopInt()
    538 			ai, stk = stk.PopInt()
    539 			stk = stk.Push(ai | bi)
    540 
    541 		case '^': // XOR
    542 			bi, stk = stk.PopInt()
    543 			ai, stk = stk.PopInt()
    544 			stk = stk.Push(ai ^ bi)
    545 
    546 		case '~': // bit complement
    547 			ai, stk = stk.PopInt()
    548 			stk = stk.Push(ai ^ -1)
    549 
    550 		case '!': // logical NOT
    551 			ai, stk = stk.PopInt()
    552 			stk = stk.Push(ai == 0)
    553 
    554 		case '=': // numeric compare
    555 			bi, stk = stk.PopInt()
    556 			ai, stk = stk.PopInt()
    557 			stk = stk.Push(ai == bi)
    558 
    559 		case '>': // greater than, numeric
    560 			bi, stk = stk.PopInt()
    561 			ai, stk = stk.PopInt()
    562 			stk = stk.Push(ai > bi)
    563 
    564 		case '<': // less than, numeric
    565 			bi, stk = stk.PopInt()
    566 			ai, stk = stk.PopInt()
    567 			stk = stk.Push(ai < bi)
    568 
    569 		case '?': // start conditional
    570 
    571 		case ';':
    572 			skip = emit
    573 
    574 		case 't':
    575 			ai, stk = stk.PopInt()
    576 			if ai == 0 {
    577 				skip = toElse
    578 			}
    579 
    580 		case 'e':
    581 			skip = toEnd
    582 
    583 		default:
    584 			pb.PutString("%" + string(ch))
    585 		}
    586 	}
    587 
    588 	return pb.End()
    589 }
    590 
    591 // TPuts emits the string to the writer, but expands inline padding
    592 // indications (of the form $<[delay]> where [delay] is msec) to
    593 // a suitable time (unless the terminfo string indicates this isn't needed
    594 // by specifying npc - no padding).  All Terminfo based strings should be
    595 // emitted using this function.
    596 func (t *Terminfo) TPuts(w io.Writer, s string) {
    597 	for {
    598 		beg := strings.Index(s, "$<")
    599 		if beg < 0 {
    600 			// Most strings don't need padding, which is good news!
    601 			_, _ = io.WriteString(w, s)
    602 			return
    603 		}
    604 		_, _ = io.WriteString(w, s[:beg])
    605 		s = s[beg+2:]
    606 		end := strings.Index(s, ">")
    607 		if end < 0 {
    608 			// unterminated.. just emit bytes unadulterated
    609 			_, _ = io.WriteString(w, "$<"+s)
    610 			return
    611 		}
    612 		val := s[:end]
    613 		s = s[end+1:]
    614 		padus := 0
    615 		unit := time.Millisecond
    616 		dot := false
    617 	loop:
    618 		for i := range val {
    619 			switch val[i] {
    620 			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
    621 				padus *= 10
    622 				padus += int(val[i] - '0')
    623 				if dot {
    624 					unit /= 10
    625 				}
    626 			case '.':
    627 				if !dot {
    628 					dot = true
    629 				} else {
    630 					break loop
    631 				}
    632 			default:
    633 				break loop
    634 			}
    635 		}
    636 
    637 		// Curses historically uses padding to achieve "fine grained"
    638 		// delays. We have much better clocks these days, and so we
    639 		// do not rely on padding but simply sleep a bit.
    640 		if len(t.PadChar) > 0 {
    641 			time.Sleep(unit * time.Duration(padus))
    642 		}
    643 	}
    644 }
    645 
    646 // TGoto returns a string suitable for addressing the cursor at the given
    647 // row and column.  The origin 0, 0 is in the upper left corner of the screen.
    648 func (t *Terminfo) TGoto(col, row int) string {
    649 	return t.TParm(t.SetCursor, row, col)
    650 }
    651 
    652 // TColor returns a string corresponding to the given foreground and background
    653 // colors.  Either fg or bg can be set to -1 to elide.
    654 func (t *Terminfo) TColor(fi, bi int) string {
    655 	rv := ""
    656 	// As a special case, we map bright colors to lower versions if the
    657 	// color table only holds 8.  For the remaining 240 colors, the user
    658 	// is out of luck.  Someday we could create a mapping table, but its
    659 	// not worth it.
    660 	if t.Colors == 8 {
    661 		if fi > 7 && fi < 16 {
    662 			fi -= 8
    663 		}
    664 		if bi > 7 && bi < 16 {
    665 			bi -= 8
    666 		}
    667 	}
    668 	if t.Colors > fi && fi >= 0 {
    669 		rv += t.TParm(t.SetFg, fi)
    670 	}
    671 	if t.Colors > bi && bi >= 0 {
    672 		rv += t.TParm(t.SetBg, bi)
    673 	}
    674 	return rv
    675 }
    676 
    677 var (
    678 	dblock    sync.Mutex
    679 	terminfos = make(map[string]*Terminfo)
    680 )
    681 
    682 // AddTerminfo can be called to register a new Terminfo entry.
    683 func AddTerminfo(t *Terminfo) {
    684 	dblock.Lock()
    685 	terminfos[t.Name] = t
    686 	for _, x := range t.Aliases {
    687 		terminfos[x] = t
    688 	}
    689 	dblock.Unlock()
    690 }
    691 
    692 // LookupTerminfo attempts to find a definition for the named $TERM.
    693 func LookupTerminfo(name string) (*Terminfo, error) {
    694 	if name == "" {
    695 		// else on windows: index out of bounds
    696 		// on the name[0] reference below
    697 		return nil, ErrTermNotFound
    698 	}
    699 
    700 	addtruecolor := false
    701 	add256color := false
    702 	switch os.Getenv("COLORTERM") {
    703 	case "truecolor", "24bit", "24-bit":
    704 		addtruecolor = true
    705 	}
    706 	dblock.Lock()
    707 	t := terminfos[name]
    708 	dblock.Unlock()
    709 
    710 	// If the name ends in -truecolor, then fabricate an entry
    711 	// from the corresponding -256color, -color, or bare terminal.
    712 	if t != nil && t.TrueColor {
    713 		addtruecolor = true
    714 	} else if t == nil && strings.HasSuffix(name, "-truecolor") {
    715 
    716 		suffixes := []string{
    717 			"-256color",
    718 			"-88color",
    719 			"-color",
    720 			"",
    721 		}
    722 		base := name[:len(name)-len("-truecolor")]
    723 		for _, s := range suffixes {
    724 			if t, _ = LookupTerminfo(base + s); t != nil {
    725 				addtruecolor = true
    726 				break
    727 			}
    728 		}
    729 	}
    730 
    731 	// If the name ends in -256color, maybe fabricate using the xterm 256 color sequences
    732 	if t == nil && strings.HasSuffix(name, "-256color") {
    733 		suffixes := []string{
    734 			"-88color",
    735 			"-color",
    736 		}
    737 		base := name[:len(name)-len("-256color")]
    738 		for _, s := range suffixes {
    739 			if t, _ = LookupTerminfo(base + s); t != nil {
    740 				add256color = true
    741 				break
    742 			}
    743 		}
    744 	}
    745 
    746 	if t == nil {
    747 		return nil, ErrTermNotFound
    748 	}
    749 
    750 	switch os.Getenv("TCELL_TRUECOLOR") {
    751 	case "":
    752 	case "disable":
    753 		addtruecolor = false
    754 	default:
    755 		addtruecolor = true
    756 	}
    757 
    758 	// If the user has requested 24-bit color with $COLORTERM, then
    759 	// amend the value (unless already present).  This means we don't
    760 	// need to have a value present.
    761 	if addtruecolor &&
    762 		t.SetFgBgRGB == "" &&
    763 		t.SetFgRGB == "" &&
    764 		t.SetBgRGB == "" {
    765 
    766 		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
    767 		t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
    768 		t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
    769 		t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
    770 			"48;2;%p4%d;%p5%d;%p6%dm"
    771 	}
    772 
    773 	if add256color {
    774 		t.Colors = 256
    775 		t.SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
    776 		t.SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
    777 		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"
    778 		t.ResetFgBg = "\x1b[39;49m"
    779 	}
    780 	return t, nil
    781 }