...

Source file src/github.com/gdamore/tcell/v2/terminfo/terminfo.go

Documentation: github.com/gdamore/tcell/v2/terminfo

     1  // Copyright 2022 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  	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  // static vars
   284  var svars [26]string
   285  
   286  type paramsBuffer struct {
   287  	out bytes.Buffer
   288  	buf bytes.Buffer
   289  }
   290  
   291  // Start initializes the params buffer with the initial string data.
   292  // It also locks the paramsBuffer.  The caller must call End() when
   293  // finished.
   294  func (pb *paramsBuffer) Start(s string) {
   295  	pb.out.Reset()
   296  	pb.buf.Reset()
   297  	pb.buf.WriteString(s)
   298  }
   299  
   300  // End returns the final output from TParam, but it also releases the lock.
   301  func (pb *paramsBuffer) End() string {
   302  	s := pb.out.String()
   303  	return s
   304  }
   305  
   306  // NextCh returns the next input character to the expander.
   307  func (pb *paramsBuffer) NextCh() (byte, error) {
   308  	return pb.buf.ReadByte()
   309  }
   310  
   311  // PutCh "emits" (rather schedules for output) a single byte character.
   312  func (pb *paramsBuffer) PutCh(ch byte) {
   313  	pb.out.WriteByte(ch)
   314  }
   315  
   316  // PutString schedules a string for output.
   317  func (pb *paramsBuffer) PutString(s string) {
   318  	pb.out.WriteString(s)
   319  }
   320  
   321  // TParm takes a terminfo parameterized string, such as setaf or cup, and
   322  // evaluates the string, and returns the result with the parameter
   323  // applied.
   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 = &paramsBuffer{}
   331  
   332  	pb.Start(s)
   333  
   334  	// make sure we always have 9 parameters -- makes it easier
   335  	// later to skip checks
   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  			// XXX Error
   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 '%': // quoted %
   381  			pb.PutCh(ch)
   382  
   383  		case 'i': // increment both parameters (ANSI cup support)
   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  			// NB: 's', 'c', and 'd' below are special cased for
   393  			// efficiency.  They could be handled by the richer
   394  			// format support below, less efficiently.
   395  			a, stk = stk.PopString()
   396  			pb.PutString(a)
   397  
   398  		case 'c':
   399  			// Integer as special character.
   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  			// This is pretty suboptimal, but this is rarely used.
   409  			// None of the mainstream terminals use any of this,
   410  			// and it would surprise me if this code is ever
   411  			// executed outside test cases.
   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': // push parameter
   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': // pop & store variable
   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': // recall & push variable
   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 '\'': // push(char) - the integer value of it
   463  			ch, _ = pb.NextCh()
   464  			_, _ = pb.NextCh() // must be ' but we don't check
   465  			stk = stk.Push(int(ch))
   466  
   467  		case '{': // push(int)
   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  			// ch must be '}' but no verification
   476  			stk = stk.Push(ai)
   477  
   478  		case 'l': // push(strlen(pop))
   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': // push(pop mod pop)
   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 '&': // AND
   516  			bi, stk = stk.PopInt()
   517  			ai, stk = stk.PopInt()
   518  			stk = stk.Push(ai & bi)
   519  
   520  		case '|': // OR
   521  			bi, stk = stk.PopInt()
   522  			ai, stk = stk.PopInt()
   523  			stk = stk.Push(ai | bi)
   524  
   525  		case '^': // XOR
   526  			bi, stk = stk.PopInt()
   527  			ai, stk = stk.PopInt()
   528  			stk = stk.Push(ai ^ bi)
   529  
   530  		case '~': // bit complement
   531  			ai, stk = stk.PopInt()
   532  			stk = stk.Push(ai ^ -1)
   533  
   534  		case '!': // logical NOT
   535  			ai, stk = stk.PopInt()
   536  			stk = stk.Push(ai == 0)
   537  
   538  		case '=': // numeric compare
   539  			bi, stk = stk.PopInt()
   540  			ai, stk = stk.PopInt()
   541  			stk = stk.Push(ai == bi)
   542  
   543  		case '>': // greater than, numeric
   544  			bi, stk = stk.PopInt()
   545  			ai, stk = stk.PopInt()
   546  			stk = stk.Push(ai > bi)
   547  
   548  		case '<': // less than, numeric
   549  			bi, stk = stk.PopInt()
   550  			ai, stk = stk.PopInt()
   551  			stk = stk.Push(ai < bi)
   552  
   553  		case '?': // start conditional
   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  // TPuts emits the string to the writer, but expands inline padding
   576  // indications (of the form $<[delay]> where [delay] is msec) to
   577  // a suitable time (unless the terminfo string indicates this isn't needed
   578  // by specifying npc - no padding).  All Terminfo based strings should be
   579  // emitted using this function.
   580  func (t *Terminfo) TPuts(w io.Writer, s string) {
   581  	for {
   582  		beg := strings.Index(s, "$<")
   583  		if beg < 0 {
   584  			// Most strings don't need padding, which is good news!
   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  			// unterminated.. just emit bytes unadulterated
   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  		// Curses historically uses padding to achieve "fine grained"
   622  		// delays. We have much better clocks these days, and so we
   623  		// do not rely on padding but simply sleep a bit.
   624  		if len(t.PadChar) > 0 {
   625  			time.Sleep(unit * time.Duration(padus))
   626  		}
   627  	}
   628  }
   629  
   630  // TGoto returns a string suitable for addressing the cursor at the given
   631  // row and column.  The origin 0, 0 is in the upper left corner of the screen.
   632  func (t *Terminfo) TGoto(col, row int) string {
   633  	return t.TParm(t.SetCursor, row, col)
   634  }
   635  
   636  // TColor returns a string corresponding to the given foreground and background
   637  // colors.  Either fg or bg can be set to -1 to elide.
   638  func (t *Terminfo) TColor(fi, bi int) string {
   639  	rv := ""
   640  	// As a special case, we map bright colors to lower versions if the
   641  	// color table only holds 8.  For the remaining 240 colors, the user
   642  	// is out of luck.  Someday we could create a mapping table, but its
   643  	// not worth it.
   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  // AddTerminfo can be called to register a new Terminfo entry.
   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  // LookupTerminfo attempts to find a definition for the named $TERM.
   677  func LookupTerminfo(name string) (*Terminfo, error) {
   678  	if name == "" {
   679  		// else on windows: index out of bounds
   680  		// on the name[0] reference below
   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  	// If the name ends in -truecolor, then fabricate an entry
   695  	// from the corresponding -256color, -color, or bare terminal.
   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  	// If the name ends in -256color, maybe fabricate using the xterm 256 color sequences
   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  	// If the user has requested 24-bit color with $COLORTERM, then
   743  	// amend the value (unless already present).  This means we don't
   744  	// need to have a value present.
   745  	if addtruecolor &&
   746  		t.SetFgBgRGB == "" &&
   747  		t.SetFgRGB == "" &&
   748  		t.SetBgRGB == "" {
   749  
   750  		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
   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