mirror of
				https://github.com/taigrr/wtf
				synced 2025-01-18 04:03:14 -08:00 
			
		
		
		
	
		
			
				
	
	
		
			924 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			924 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 The TCell Authors
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use file except in compliance with the License.
 | 
						|
// You may obtain a copy of the license at
 | 
						|
//
 | 
						|
//    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package terminfo
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"compress/gzip"
 | 
						|
	"crypto/sha1"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrTermNotFound indicates that a suitable terminal entry could
 | 
						|
	// not be found.  This can result from either not having TERM set,
 | 
						|
	// or from the TERM failing to support certain minimal functionality,
 | 
						|
	// in particular absolute cursor addressability (the cup capability)
 | 
						|
	// is required.  For example, legacy "adm3" lacks this capability,
 | 
						|
	// whereas the slightly newer "adm3a" supports it.  This failure
 | 
						|
	// occurs most often with "dumb".
 | 
						|
	ErrTermNotFound = errors.New("terminal entry not found")
 | 
						|
)
 | 
						|
 | 
						|
// Terminfo represents a terminfo entry.  Note that we use friendly names
 | 
						|
// in Go, but when we write out JSON, we use the same names as terminfo.
 | 
						|
// The name, aliases and smous, rmous fields do not come from terminfo directly.
 | 
						|
type Terminfo struct {
 | 
						|
	Name         string   `json:"name"`
 | 
						|
	Aliases      []string `json:"aliases,omitempty"`
 | 
						|
	Columns      int      `json:"cols,omitempty"`   // cols
 | 
						|
	Lines        int      `json:"lines,omitempty"`  // lines
 | 
						|
	Colors       int      `json:"colors,omitempty"` // colors
 | 
						|
	Bell         string   `json:"bell,omitempty"`   // bell
 | 
						|
	Clear        string   `json:"clear,omitempty"`  // clear
 | 
						|
	EnterCA      string   `json:"smcup,omitempty"`  // smcup
 | 
						|
	ExitCA       string   `json:"rmcup,omitempty"`  // rmcup
 | 
						|
	ShowCursor   string   `json:"cnorm,omitempty"`  // cnorm
 | 
						|
	HideCursor   string   `json:"civis,omitempty"`  // civis
 | 
						|
	AttrOff      string   `json:"sgr0,omitempty"`   // sgr0
 | 
						|
	Underline    string   `json:"smul,omitempty"`   // smul
 | 
						|
	Bold         string   `json:"bold,omitempty"`   // bold
 | 
						|
	Blink        string   `json:"blink,omitempty"`  // blink
 | 
						|
	Reverse      string   `json:"rev,omitempty"`    // rev
 | 
						|
	Dim          string   `json:"dim,omitempty"`    // dim
 | 
						|
	EnterKeypad  string   `json:"smkx,omitempty"`   // smkx
 | 
						|
	ExitKeypad   string   `json:"rmkx,omitempty"`   // rmkx
 | 
						|
	SetFg        string   `json:"setaf,omitempty"`  // setaf
 | 
						|
	SetBg        string   `json:"setbg,omitempty"`  // setab
 | 
						|
	SetCursor    string   `json:"cup,omitempty"`    // cup
 | 
						|
	CursorBack1  string   `json:"cub1,omitempty"`   // cub1
 | 
						|
	CursorUp1    string   `json:"cuu1,omitempty"`   // cuu1
 | 
						|
	PadChar      string   `json:"pad,omitempty"`    // pad
 | 
						|
	KeyBackspace string   `json:"kbs,omitempty"`    // kbs
 | 
						|
	KeyF1        string   `json:"kf1,omitempty"`    // kf1
 | 
						|
	KeyF2        string   `json:"kf2,omitempty"`    // kf2
 | 
						|
	KeyF3        string   `json:"kf3,omitempty"`    // kf3
 | 
						|
	KeyF4        string   `json:"kf4,omitempty"`    // kf4
 | 
						|
	KeyF5        string   `json:"kf5,omitempty"`    // kf5
 | 
						|
	KeyF6        string   `json:"kf6,omitempty"`    // kf6
 | 
						|
	KeyF7        string   `json:"kf7,omitempty"`    // kf7
 | 
						|
	KeyF8        string   `json:"kf8,omitempty"`    // kf8
 | 
						|
	KeyF9        string   `json:"kf9,omitempty"`    // kf9
 | 
						|
	KeyF10       string   `json:"kf10,omitempty"`   // kf10
 | 
						|
	KeyF11       string   `json:"kf11,omitempty"`   // kf11
 | 
						|
	KeyF12       string   `json:"kf12,omitempty"`   // kf12
 | 
						|
	KeyF13       string   `json:"kf13,omitempty"`   // kf13
 | 
						|
	KeyF14       string   `json:"kf14,omitempty"`   // kf14
 | 
						|
	KeyF15       string   `json:"kf15,omitempty"`   // kf15
 | 
						|
	KeyF16       string   `json:"kf16,omitempty"`   // kf16
 | 
						|
	KeyF17       string   `json:"kf17,omitempty"`   // kf17
 | 
						|
	KeyF18       string   `json:"kf18,omitempty"`   // kf18
 | 
						|
	KeyF19       string   `json:"kf19,omitempty"`   // kf19
 | 
						|
	KeyF20       string   `json:"kf20,omitempty"`   // kf20
 | 
						|
	KeyF21       string   `json:"kf21,omitempty"`   // kf21
 | 
						|
	KeyF22       string   `json:"kf22,omitempty"`   // kf22
 | 
						|
	KeyF23       string   `json:"kf23,omitempty"`   // kf23
 | 
						|
	KeyF24       string   `json:"kf24,omitempty"`   // kf24
 | 
						|
	KeyF25       string   `json:"kf25,omitempty"`   // kf25
 | 
						|
	KeyF26       string   `json:"kf26,omitempty"`   // kf26
 | 
						|
	KeyF27       string   `json:"kf27,omitempty"`   // kf27
 | 
						|
	KeyF28       string   `json:"kf28,omitempty"`   // kf28
 | 
						|
	KeyF29       string   `json:"kf29,omitempty"`   // kf29
 | 
						|
	KeyF30       string   `json:"kf30,omitempty"`   // kf30
 | 
						|
	KeyF31       string   `json:"kf31,omitempty"`   // kf31
 | 
						|
	KeyF32       string   `json:"kf32,omitempty"`   // kf32
 | 
						|
	KeyF33       string   `json:"kf33,omitempty"`   // kf33
 | 
						|
	KeyF34       string   `json:"kf34,omitempty"`   // kf34
 | 
						|
	KeyF35       string   `json:"kf35,omitempty"`   // kf35
 | 
						|
	KeyF36       string   `json:"kf36,omitempty"`   // kf36
 | 
						|
	KeyF37       string   `json:"kf37,omitempty"`   // kf37
 | 
						|
	KeyF38       string   `json:"kf38,omitempty"`   // kf38
 | 
						|
	KeyF39       string   `json:"kf39,omitempty"`   // kf39
 | 
						|
	KeyF40       string   `json:"kf40,omitempty"`   // kf40
 | 
						|
	KeyF41       string   `json:"kf41,omitempty"`   // kf41
 | 
						|
	KeyF42       string   `json:"kf42,omitempty"`   // kf42
 | 
						|
	KeyF43       string   `json:"kf43,omitempty"`   // kf43
 | 
						|
	KeyF44       string   `json:"kf44,omitempty"`   // kf44
 | 
						|
	KeyF45       string   `json:"kf45,omitempty"`   // kf45
 | 
						|
	KeyF46       string   `json:"kf46,omitempty"`   // kf46
 | 
						|
	KeyF47       string   `json:"kf47,omitempty"`   // kf47
 | 
						|
	KeyF48       string   `json:"kf48,omitempty"`   // kf48
 | 
						|
	KeyF49       string   `json:"kf49,omitempty"`   // kf49
 | 
						|
	KeyF50       string   `json:"kf50,omitempty"`   // kf50
 | 
						|
	KeyF51       string   `json:"kf51,omitempty"`   // kf51
 | 
						|
	KeyF52       string   `json:"kf52,omitempty"`   // kf52
 | 
						|
	KeyF53       string   `json:"kf53,omitempty"`   // kf53
 | 
						|
	KeyF54       string   `json:"kf54,omitempty"`   // kf54
 | 
						|
	KeyF55       string   `json:"kf55,omitempty"`   // kf55
 | 
						|
	KeyF56       string   `json:"kf56,omitempty"`   // kf56
 | 
						|
	KeyF57       string   `json:"kf57,omitempty"`   // kf57
 | 
						|
	KeyF58       string   `json:"kf58,omitempty"`   // kf58
 | 
						|
	KeyF59       string   `json:"kf59,omitempty"`   // kf59
 | 
						|
	KeyF60       string   `json:"kf60,omitempty"`   // kf60
 | 
						|
	KeyF61       string   `json:"kf61,omitempty"`   // kf61
 | 
						|
	KeyF62       string   `json:"kf62,omitempty"`   // kf62
 | 
						|
	KeyF63       string   `json:"kf63,omitempty"`   // kf63
 | 
						|
	KeyF64       string   `json:"kf64,omitempty"`   // kf64
 | 
						|
	KeyInsert    string   `json:"kich,omitempty"`   // kich1
 | 
						|
	KeyDelete    string   `json:"kdch,omitempty"`   // kdch1
 | 
						|
	KeyHome      string   `json:"khome,omitempty"`  // khome
 | 
						|
	KeyEnd       string   `json:"kend,omitempty"`   // kend
 | 
						|
	KeyHelp      string   `json:"khlp,omitempty"`   // khlp
 | 
						|
	KeyPgUp      string   `json:"kpp,omitempty"`    // kpp
 | 
						|
	KeyPgDn      string   `json:"knp,omitempty"`    // knp
 | 
						|
	KeyUp        string   `json:"kcuu1,omitempty"`  // kcuu1
 | 
						|
	KeyDown      string   `json:"kcud1,omitempty"`  // kcud1
 | 
						|
	KeyLeft      string   `json:"kcub1,omitempty"`  // kcub1
 | 
						|
	KeyRight     string   `json:"kcuf1,omitempty"`  // kcuf1
 | 
						|
	KeyBacktab   string   `json:"kcbt,omitempty"`   // kcbt
 | 
						|
	KeyExit      string   `json:"kext,omitempty"`   // kext
 | 
						|
	KeyClear     string   `json:"kclr,omitempty"`   // kclr
 | 
						|
	KeyPrint     string   `json:"kprt,omitempty"`   // kprt
 | 
						|
	KeyCancel    string   `json:"kcan,omitempty"`   // kcan
 | 
						|
	Mouse        string   `json:"kmous,omitempty"`  // kmous
 | 
						|
	MouseMode    string   `json:"XM,omitempty"`     // XM
 | 
						|
	AltChars     string   `json:"acsc,omitempty"`   // acsc
 | 
						|
	EnterAcs     string   `json:"smacs,omitempty"`  // smacs
 | 
						|
	ExitAcs      string   `json:"rmacs,omitempty"`  // rmacs
 | 
						|
	EnableAcs    string   `json:"enacs,omitempty"`  // enacs
 | 
						|
	KeyShfRight  string   `json:"kRIT,omitempty"`   // kRIT
 | 
						|
	KeyShfLeft   string   `json:"kLFT,omitempty"`   // kLFT
 | 
						|
	KeyShfHome   string   `json:"kHOM,omitempty"`   // kHOM
 | 
						|
	KeyShfEnd    string   `json:"kEND,omitempty"`   // kEND
 | 
						|
 | 
						|
	// These are non-standard extensions to terminfo.  This includes
 | 
						|
	// true color support, and some additional keys.  Its kind of bizarre
 | 
						|
	// that shifted variants of left and right exist, but not up and down.
 | 
						|
	// Terminal support for these are going to vary amongst XTerm
 | 
						|
	// emulations, so don't depend too much on them in your application.
 | 
						|
 | 
						|
	SetFgBg         string `json:"_setfgbg,omitempty"`    // setfgbg
 | 
						|
	SetFgBgRGB      string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
 | 
						|
	SetFgRGB        string `json:"_setfrgb,omitempty"`    // setfrgb
 | 
						|
	SetBgRGB        string `json:"_setbrgb,omitempty"`    // setbrgb
 | 
						|
	KeyShfUp        string `json:"_kscu1,omitempty"`      // shift-up
 | 
						|
	KeyShfDown      string `json:"_kscud1,omitempty"`     // shift-down
 | 
						|
	KeyCtrlUp       string `json:"_kccu1,omitempty"`      // ctrl-up
 | 
						|
	KeyCtrlDown     string `json:"_kccud1,omitempty"`     // ctrl-left
 | 
						|
	KeyCtrlRight    string `json:"_kccuf1,omitempty"`     // ctrl-right
 | 
						|
	KeyCtrlLeft     string `json:"_kccub1,omitempty"`     // ctrl-left
 | 
						|
	KeyMetaUp       string `json:"_kmcu1,omitempty"`      // meta-up
 | 
						|
	KeyMetaDown     string `json:"_kmcud1,omitempty"`     // meta-left
 | 
						|
	KeyMetaRight    string `json:"_kmcuf1,omitempty"`     // meta-right
 | 
						|
	KeyMetaLeft     string `json:"_kmcub1,omitempty"`     // meta-left
 | 
						|
	KeyAltUp        string `json:"_kacu1,omitempty"`      // alt-up
 | 
						|
	KeyAltDown      string `json:"_kacud1,omitempty"`     // alt-left
 | 
						|
	KeyAltRight     string `json:"_kacuf1,omitempty"`     // alt-right
 | 
						|
	KeyAltLeft      string `json:"_kacub1,omitempty"`     // alt-left
 | 
						|
	KeyCtrlHome     string `json:"_kchome,omitempty"`
 | 
						|
	KeyCtrlEnd      string `json:"_kcend,omitempty"`
 | 
						|
	KeyMetaHome     string `json:"_kmhome,omitempty"`
 | 
						|
	KeyMetaEnd      string `json:"_kmend,omitempty"`
 | 
						|
	KeyAltHome      string `json:"_kahome,omitempty"`
 | 
						|
	KeyAltEnd       string `json:"_kaend,omitempty"`
 | 
						|
	KeyAltShfUp     string `json:"_kascu1,omitempty"`
 | 
						|
	KeyAltShfDown   string `json:"_kascud1,omitempty"`
 | 
						|
	KeyAltShfLeft   string `json:"_kascub1,omitempty"`
 | 
						|
	KeyAltShfRight  string `json:"_kascuf1,omitempty"`
 | 
						|
	KeyMetaShfUp    string `json:"_kmscu1,omitempty"`
 | 
						|
	KeyMetaShfDown  string `json:"_kmscud1,omitempty"`
 | 
						|
	KeyMetaShfLeft  string `json:"_kmscub1,omitempty"`
 | 
						|
	KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
 | 
						|
	KeyCtrlShfUp    string `json:"_kcscu1,omitempty"`
 | 
						|
	KeyCtrlShfDown  string `json:"_kcscud1,omitempty"`
 | 
						|
	KeyCtrlShfLeft  string `json:"_kcscub1,omitempty"`
 | 
						|
	KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
 | 
						|
	KeyCtrlShfHome  string `json:"_kcHOME,omitempty"`
 | 
						|
	KeyCtrlShfEnd   string `json:"_kcEND,omitempty"`
 | 
						|
	KeyAltShfHome   string `json:"_kaHOME,omitempty"`
 | 
						|
	KeyAltShfEnd    string `json:"_kaEND,omitempty"`
 | 
						|
	KeyMetaShfHome  string `json:"_kmHOME,omitempty"`
 | 
						|
	KeyMetaShfEnd   string `json:"_kmEND,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type stackElem struct {
 | 
						|
	s     string
 | 
						|
	i     int
 | 
						|
	isStr bool
 | 
						|
	isInt bool
 | 
						|
}
 | 
						|
 | 
						|
type stack []stackElem
 | 
						|
 | 
						|
func (st stack) Push(v string) stack {
 | 
						|
	e := stackElem{
 | 
						|
		s:     v,
 | 
						|
		isStr: true,
 | 
						|
	}
 | 
						|
	return append(st, e)
 | 
						|
}
 | 
						|
 | 
						|
func (st stack) Pop() (string, stack) {
 | 
						|
	v := ""
 | 
						|
	if len(st) > 0 {
 | 
						|
		e := st[len(st)-1]
 | 
						|
		st = st[:len(st)-1]
 | 
						|
		if e.isStr {
 | 
						|
			v = e.s
 | 
						|
		} else {
 | 
						|
			v = strconv.Itoa(e.i)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return v, st
 | 
						|
}
 | 
						|
 | 
						|
func (st stack) PopInt() (int, stack) {
 | 
						|
	if len(st) > 0 {
 | 
						|
		e := st[len(st)-1]
 | 
						|
		st = st[:len(st)-1]
 | 
						|
		if e.isInt {
 | 
						|
			return e.i, st
 | 
						|
		} else if e.isStr {
 | 
						|
			i, _ := strconv.Atoi(e.s)
 | 
						|
			return i, st
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0, st
 | 
						|
}
 | 
						|
 | 
						|
func (st stack) PopBool() (bool, stack) {
 | 
						|
	if len(st) > 0 {
 | 
						|
		e := st[len(st)-1]
 | 
						|
		st = st[:len(st)-1]
 | 
						|
		if e.isStr {
 | 
						|
			if e.s == "1" {
 | 
						|
				return true, st
 | 
						|
			}
 | 
						|
			return false, st
 | 
						|
		} else if e.i == 1 {
 | 
						|
			return true, st
 | 
						|
		} else {
 | 
						|
			return false, st
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false, st
 | 
						|
}
 | 
						|
 | 
						|
func (st stack) PushInt(i int) stack {
 | 
						|
	e := stackElem{
 | 
						|
		i:     i,
 | 
						|
		isInt: true,
 | 
						|
	}
 | 
						|
	return append(st, e)
 | 
						|
}
 | 
						|
 | 
						|
func (st stack) PushBool(i bool) stack {
 | 
						|
	if i {
 | 
						|
		return st.PushInt(1)
 | 
						|
	}
 | 
						|
	return st.PushInt(0)
 | 
						|
}
 | 
						|
 | 
						|
func nextch(s string, index int) (byte, int) {
 | 
						|
	if index < len(s) {
 | 
						|
		return s[index], index + 1
 | 
						|
	}
 | 
						|
	return 0, index
 | 
						|
}
 | 
						|
 | 
						|
// static vars
 | 
						|
var svars [26]string
 | 
						|
 | 
						|
// paramsBuffer handles some persistent state for TParam.  Technically we
 | 
						|
// could probably dispense with this, but caching buffer arrays gives us
 | 
						|
// a nice little performance boost.  Furthermore, we know that TParam is
 | 
						|
// rarely (never?) called re-entrantly, so we can just reuse the same
 | 
						|
// buffers, making it thread-safe by stashing a lock.
 | 
						|
type paramsBuffer struct {
 | 
						|
	out bytes.Buffer
 | 
						|
	buf bytes.Buffer
 | 
						|
	lk  sync.Mutex
 | 
						|
}
 | 
						|
 | 
						|
// Start initializes the params buffer with the initial string data.
 | 
						|
// It also locks the paramsBuffer.  The caller must call End() when
 | 
						|
// finished.
 | 
						|
func (pb *paramsBuffer) Start(s string) {
 | 
						|
	pb.lk.Lock()
 | 
						|
	pb.out.Reset()
 | 
						|
	pb.buf.Reset()
 | 
						|
	pb.buf.WriteString(s)
 | 
						|
}
 | 
						|
 | 
						|
// End returns the final output from TParam, but it also releases the lock.
 | 
						|
func (pb *paramsBuffer) End() string {
 | 
						|
	s := pb.out.String()
 | 
						|
	pb.lk.Unlock()
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
// NextCh returns the next input character to the expander.
 | 
						|
func (pb *paramsBuffer) NextCh() (byte, error) {
 | 
						|
	return pb.buf.ReadByte()
 | 
						|
}
 | 
						|
 | 
						|
// PutCh "emits" (rather schedules for output) a single byte character.
 | 
						|
func (pb *paramsBuffer) PutCh(ch byte) {
 | 
						|
	pb.out.WriteByte(ch)
 | 
						|
}
 | 
						|
 | 
						|
// PutString schedules a string for output.
 | 
						|
func (pb *paramsBuffer) PutString(s string) {
 | 
						|
	pb.out.WriteString(s)
 | 
						|
}
 | 
						|
 | 
						|
var pb = ¶msBuffer{}
 | 
						|
 | 
						|
// TParm takes a terminfo parameterized string, such as setaf or cup, and
 | 
						|
// evaluates the string, and returns the result with the parameter
 | 
						|
// applied.
 | 
						|
func (t *Terminfo) TParm(s string, p ...int) string {
 | 
						|
	var stk stack
 | 
						|
	var a, b string
 | 
						|
	var ai, bi int
 | 
						|
	var ab bool
 | 
						|
	var dvars [26]string
 | 
						|
	var params [9]int
 | 
						|
 | 
						|
	pb.Start(s)
 | 
						|
 | 
						|
	// make sure we always have 9 parameters -- makes it easier
 | 
						|
	// later to skip checks
 | 
						|
	for i := 0; i < len(params) && i < len(p); i++ {
 | 
						|
		params[i] = p[i]
 | 
						|
	}
 | 
						|
 | 
						|
	nest := 0
 | 
						|
 | 
						|
	for {
 | 
						|
 | 
						|
		ch, err := pb.NextCh()
 | 
						|
		if err != nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if ch != '%' {
 | 
						|
			pb.PutCh(ch)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		ch, err = pb.NextCh()
 | 
						|
		if err != nil {
 | 
						|
			// XXX Error
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		switch ch {
 | 
						|
		case '%': // quoted %
 | 
						|
			pb.PutCh(ch)
 | 
						|
 | 
						|
		case 'i': // increment both parameters (ANSI cup support)
 | 
						|
			params[0]++
 | 
						|
			params[1]++
 | 
						|
 | 
						|
		case 'c', 's':
 | 
						|
			// NB: these, and 'd' below are special cased for
 | 
						|
			// efficiency.  They could be handled by the richer
 | 
						|
			// format support below, less efficiently.
 | 
						|
			a, stk = stk.Pop()
 | 
						|
			pb.PutString(a)
 | 
						|
 | 
						|
		case 'd':
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			pb.PutString(strconv.Itoa(ai))
 | 
						|
 | 
						|
		case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
 | 
						|
			// This is pretty suboptimal, but this is rarely used.
 | 
						|
			// None of the mainstream terminals use any of this,
 | 
						|
			// and it would surprise me if this code is ever
 | 
						|
			// executed outside of test cases.
 | 
						|
			f := "%"
 | 
						|
			if ch == ':' {
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
			}
 | 
						|
			f += string(ch)
 | 
						|
			for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
				f += string(ch)
 | 
						|
			}
 | 
						|
			for (ch >= '0' && ch <= '9') || ch == '.' {
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
				f += string(ch)
 | 
						|
			}
 | 
						|
			switch ch {
 | 
						|
			case 'd', 'x', 'X', 'o':
 | 
						|
				ai, stk = stk.PopInt()
 | 
						|
				pb.PutString(fmt.Sprintf(f, ai))
 | 
						|
			case 'c', 's':
 | 
						|
				a, stk = stk.Pop()
 | 
						|
				pb.PutString(fmt.Sprintf(f, a))
 | 
						|
			}
 | 
						|
 | 
						|
		case 'p': // push parameter
 | 
						|
			ch, _ = pb.NextCh()
 | 
						|
			ai = int(ch - '1')
 | 
						|
			if ai >= 0 && ai < len(params) {
 | 
						|
				stk = stk.PushInt(params[ai])
 | 
						|
			} else {
 | 
						|
				stk = stk.PushInt(0)
 | 
						|
			}
 | 
						|
 | 
						|
		case 'P': // pop & store variable
 | 
						|
			ch, _ = pb.NextCh()
 | 
						|
			if ch >= 'A' && ch <= 'Z' {
 | 
						|
				svars[int(ch-'A')], stk = stk.Pop()
 | 
						|
			} else if ch >= 'a' && ch <= 'z' {
 | 
						|
				dvars[int(ch-'a')], stk = stk.Pop()
 | 
						|
			}
 | 
						|
 | 
						|
		case 'g': // recall & push variable
 | 
						|
			ch, _ = pb.NextCh()
 | 
						|
			if ch >= 'A' && ch <= 'Z' {
 | 
						|
				stk = stk.Push(svars[int(ch-'A')])
 | 
						|
			} else if ch >= 'a' && ch <= 'z' {
 | 
						|
				stk = stk.Push(dvars[int(ch-'a')])
 | 
						|
			}
 | 
						|
 | 
						|
		case '\'': // push(char)
 | 
						|
			ch, _ = pb.NextCh()
 | 
						|
			pb.NextCh() // must be ' but we don't check
 | 
						|
			stk = stk.Push(string(ch))
 | 
						|
 | 
						|
		case '{': // push(int)
 | 
						|
			ai = 0
 | 
						|
			ch, _ = pb.NextCh()
 | 
						|
			for ch >= '0' && ch <= '9' {
 | 
						|
				ai *= 10
 | 
						|
				ai += int(ch - '0')
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
			}
 | 
						|
			// ch must be '}' but no verification
 | 
						|
			stk = stk.PushInt(ai)
 | 
						|
 | 
						|
		case 'l': // push(strlen(pop))
 | 
						|
			a, stk = stk.Pop()
 | 
						|
			stk = stk.PushInt(len(a))
 | 
						|
 | 
						|
		case '+':
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai + bi)
 | 
						|
 | 
						|
		case '-':
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai - bi)
 | 
						|
 | 
						|
		case '*':
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai * bi)
 | 
						|
 | 
						|
		case '/':
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			if bi != 0 {
 | 
						|
				stk = stk.PushInt(ai / bi)
 | 
						|
			} else {
 | 
						|
				stk = stk.PushInt(0)
 | 
						|
			}
 | 
						|
 | 
						|
		case 'm': // push(pop mod pop)
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			if bi != 0 {
 | 
						|
				stk = stk.PushInt(ai % bi)
 | 
						|
			} else {
 | 
						|
				stk = stk.PushInt(0)
 | 
						|
			}
 | 
						|
 | 
						|
		case '&': // AND
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai & bi)
 | 
						|
 | 
						|
		case '|': // OR
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai | bi)
 | 
						|
 | 
						|
		case '^': // XOR
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai ^ bi)
 | 
						|
 | 
						|
		case '~': // bit complement
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushInt(ai ^ -1)
 | 
						|
 | 
						|
		case '!': // logical NOT
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushBool(ai != 0)
 | 
						|
 | 
						|
		case '=': // numeric compare or string compare
 | 
						|
			b, stk = stk.Pop()
 | 
						|
			a, stk = stk.Pop()
 | 
						|
			stk = stk.PushBool(a == b)
 | 
						|
 | 
						|
		case '>': // greater than, numeric
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushBool(ai > bi)
 | 
						|
 | 
						|
		case '<': // less than, numeric
 | 
						|
			bi, stk = stk.PopInt()
 | 
						|
			ai, stk = stk.PopInt()
 | 
						|
			stk = stk.PushBool(ai < bi)
 | 
						|
 | 
						|
		case '?': // start conditional
 | 
						|
 | 
						|
		case 't':
 | 
						|
			ab, stk = stk.PopBool()
 | 
						|
			if ab {
 | 
						|
				// just keep going
 | 
						|
				break
 | 
						|
			}
 | 
						|
			nest = 0
 | 
						|
		ifloop:
 | 
						|
			// this loop consumes everything until we hit our else,
 | 
						|
			// or the end of the conditional
 | 
						|
			for {
 | 
						|
				ch, err = pb.NextCh()
 | 
						|
				if err != nil {
 | 
						|
					break
 | 
						|
				}
 | 
						|
				if ch != '%' {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
				switch ch {
 | 
						|
				case ';':
 | 
						|
					if nest == 0 {
 | 
						|
						break ifloop
 | 
						|
					}
 | 
						|
					nest--
 | 
						|
				case '?':
 | 
						|
					nest++
 | 
						|
				case 'e':
 | 
						|
					if nest == 0 {
 | 
						|
						break ifloop
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		case 'e':
 | 
						|
			// if we got here, it means we didn't use the else
 | 
						|
			// in the 't' case above, and we should skip until
 | 
						|
			// the end of the conditional
 | 
						|
			nest = 0
 | 
						|
		elloop:
 | 
						|
			for {
 | 
						|
				ch, err = pb.NextCh()
 | 
						|
				if err != nil {
 | 
						|
					break
 | 
						|
				}
 | 
						|
				if ch != '%' {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				ch, _ = pb.NextCh()
 | 
						|
				switch ch {
 | 
						|
				case ';':
 | 
						|
					if nest == 0 {
 | 
						|
						break elloop
 | 
						|
					}
 | 
						|
					nest--
 | 
						|
				case '?':
 | 
						|
					nest++
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		case ';': // endif
 | 
						|
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return pb.End()
 | 
						|
}
 | 
						|
 | 
						|
// TPuts emits the string to the writer, but expands inline padding
 | 
						|
// indications (of the form $<[delay]> where [delay] is msec) to
 | 
						|
// a suitable number of padding characters (usually null bytes) based
 | 
						|
// upon the supplied baud.  At high baud rates, more padding characters
 | 
						|
// will be inserted.  All Terminfo based strings should be emitted using
 | 
						|
// this function.
 | 
						|
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
 | 
						|
	for {
 | 
						|
		beg := strings.Index(s, "$<")
 | 
						|
		if beg < 0 {
 | 
						|
			// Most strings don't need padding, which is good news!
 | 
						|
			io.WriteString(w, s)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		io.WriteString(w, s[:beg])
 | 
						|
		s = s[beg+2:]
 | 
						|
		end := strings.Index(s, ">")
 | 
						|
		if end < 0 {
 | 
						|
			// unterminated.. just emit bytes unadulterated
 | 
						|
			io.WriteString(w, "$<"+s)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		val := s[:end]
 | 
						|
		s = s[end+1:]
 | 
						|
		padus := 0
 | 
						|
		unit := 1000
 | 
						|
		dot := false
 | 
						|
	loop:
 | 
						|
		for i := range val {
 | 
						|
			switch val[i] {
 | 
						|
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
 | 
						|
				padus *= 10
 | 
						|
				padus += int(val[i] - '0')
 | 
						|
				if dot {
 | 
						|
					unit *= 10
 | 
						|
				}
 | 
						|
			case '.':
 | 
						|
				if !dot {
 | 
						|
					dot = true
 | 
						|
				} else {
 | 
						|
					break loop
 | 
						|
				}
 | 
						|
			default:
 | 
						|
				break loop
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cnt := int(((baud / 8) * padus) / unit)
 | 
						|
		for cnt > 0 {
 | 
						|
			io.WriteString(w, t.PadChar)
 | 
						|
			cnt--
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TGoto returns a string suitable for addressing the cursor at the given
 | 
						|
// row and column.  The origin 0, 0 is in the upper left corner of the screen.
 | 
						|
func (t *Terminfo) TGoto(col, row int) string {
 | 
						|
	return t.TParm(t.SetCursor, row, col)
 | 
						|
}
 | 
						|
 | 
						|
// TColor returns a string corresponding to the given foreground and background
 | 
						|
// colors.  Either fg or bg can be set to -1 to elide.
 | 
						|
func (t *Terminfo) TColor(fi, bi int) string {
 | 
						|
	rv := ""
 | 
						|
	// As a special case, we map bright colors to lower versions if the
 | 
						|
	// color table only holds 8.  For the remaining 240 colors, the user
 | 
						|
	// is out of luck.  Someday we could create a mapping table, but its
 | 
						|
	// not worth it.
 | 
						|
	if t.Colors == 8 {
 | 
						|
		if fi > 7 && fi < 16 {
 | 
						|
			fi -= 8
 | 
						|
		}
 | 
						|
		if bi > 7 && bi < 16 {
 | 
						|
			bi -= 8
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if t.Colors > fi && fi >= 0 {
 | 
						|
		rv += t.TParm(t.SetFg, fi)
 | 
						|
	}
 | 
						|
	if t.Colors > bi && bi >= 0 {
 | 
						|
		rv += t.TParm(t.SetBg, bi)
 | 
						|
	}
 | 
						|
	return rv
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	dblock    sync.Mutex
 | 
						|
	terminfos = make(map[string]*Terminfo)
 | 
						|
	aliases   = make(map[string]string)
 | 
						|
)
 | 
						|
 | 
						|
// AddTerminfo can be called to register a new Terminfo entry.
 | 
						|
func AddTerminfo(t *Terminfo) {
 | 
						|
	dblock.Lock()
 | 
						|
	terminfos[t.Name] = t
 | 
						|
	for _, x := range t.Aliases {
 | 
						|
		terminfos[x] = t
 | 
						|
	}
 | 
						|
	dblock.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
func loadFromFile(fname string, term string) (*Terminfo, error) {
 | 
						|
	var e error
 | 
						|
	var f io.Reader
 | 
						|
	if f, e = os.Open(fname); e != nil {
 | 
						|
		return nil, e
 | 
						|
	}
 | 
						|
	if strings.HasSuffix(fname, ".gz") {
 | 
						|
		if f, e = gzip.NewReader(f); e != nil {
 | 
						|
			return nil, e
 | 
						|
		}
 | 
						|
	}
 | 
						|
	d := json.NewDecoder(f)
 | 
						|
	for {
 | 
						|
		t := &Terminfo{}
 | 
						|
		if e := d.Decode(t); e != nil {
 | 
						|
			if e == io.EOF {
 | 
						|
				return nil, ErrTermNotFound
 | 
						|
			}
 | 
						|
			return nil, e
 | 
						|
		}
 | 
						|
		if t.SetCursor == "" {
 | 
						|
			// This must be an alias record, return it.
 | 
						|
			return t, nil
 | 
						|
		}
 | 
						|
		if t.Name == term {
 | 
						|
			return t, nil
 | 
						|
		}
 | 
						|
		for _, a := range t.Aliases {
 | 
						|
			if a == term {
 | 
						|
				return t, nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// LookupTerminfo attempts to find a definition for the named $TERM.
 | 
						|
// It first looks in the builtin database, which should cover just about
 | 
						|
// everyone.  If it can't find one there, then it will attempt to read
 | 
						|
// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb,
 | 
						|
// or as a database file.
 | 
						|
//
 | 
						|
// The database files are named by taking  terminal name, hashing it through
 | 
						|
// sha1, and then a subdirectory of the form database/hash[0:2]/hash[0:8]
 | 
						|
// (with an optional .gz extension).
 | 
						|
//
 | 
						|
// For other local database files, we will look for the database file using
 | 
						|
// the terminal name, so database/term[0:2]/term[0:8], again with optional
 | 
						|
// .gz extension.
 | 
						|
func LookupTerminfo(name string) (*Terminfo, error) {
 | 
						|
	if name == "" {
 | 
						|
		// else on windows: index out of bounds
 | 
						|
		// on the name[0] reference below
 | 
						|
		return nil, ErrTermNotFound
 | 
						|
	}
 | 
						|
 | 
						|
	addtruecolor := false
 | 
						|
	switch os.Getenv("COLORTERM") {
 | 
						|
	case "truecolor", "24bit", "24-bit":
 | 
						|
		addtruecolor = true
 | 
						|
	}
 | 
						|
	dblock.Lock()
 | 
						|
	t := terminfos[name]
 | 
						|
	dblock.Unlock()
 | 
						|
 | 
						|
	if t == nil {
 | 
						|
 | 
						|
		var files []string
 | 
						|
		letter := fmt.Sprintf("%02x", name[0])
 | 
						|
		gzfile := path.Join(letter, name+".gz")
 | 
						|
		jsfile := path.Join(letter, name)
 | 
						|
		hash := fmt.Sprintf("%x", sha1.Sum([]byte(name)))
 | 
						|
		gzhfile := path.Join(hash[0:2], hash[0:8]+".gz")
 | 
						|
		jshfile := path.Join(hash[0:2], hash[0:8])
 | 
						|
 | 
						|
		// Build up the search path.  Old versions of tcell used a
 | 
						|
		// single database file, whereas the new ones locate them
 | 
						|
		// in JSON (optionally compressed) files.
 | 
						|
		//
 | 
						|
		// The search path for "xterm" (SHA1 sig e2e28a8e...) looks
 | 
						|
		// like this:
 | 
						|
		//
 | 
						|
		// $TCELLDB/78/xterm.gz
 | 
						|
		// $TCELLDB/78/xterm
 | 
						|
		// $TCELLDB
 | 
						|
		// $HOME/.tcelldb/e2/e2e28a8e.gz
 | 
						|
		// $HOME/.tcelldb/e2/e2e28a8e
 | 
						|
		// $HOME/.tcelldb/78/xterm.gz
 | 
						|
		// $HOME/.tcelldb/78/xterm
 | 
						|
		// $HOME/.tcelldb
 | 
						|
		// $GOPATH/terminfo/database/e2/e2e28a8e.gz
 | 
						|
		// $GOPATH/terminfo/database/e2/e2e28a8e
 | 
						|
		// $GOPATH/terminfo/database/78/xterm.gz
 | 
						|
		// $GOPATH/terminfo/database/78/xterm
 | 
						|
		//
 | 
						|
		// Note that the legacy name lookups (78/xterm etc.) are
 | 
						|
		// provided for compatibility.  We do not actually deliver
 | 
						|
		// any files with this style of naming, to avoid collisions
 | 
						|
		// on case insensitive filesystems. (*cough* mac *cough*).
 | 
						|
 | 
						|
		// If $GOPATH set, honor it, else assume $HOME/go just like
 | 
						|
		// modern golang does.
 | 
						|
		gopath := os.Getenv("GOPATH")
 | 
						|
		if gopath == "" {
 | 
						|
			gopath = path.Join(os.Getenv("HOME"), "go")
 | 
						|
		}
 | 
						|
		if pth := os.Getenv("TCELLDB"); pth != "" {
 | 
						|
			files = append(files,
 | 
						|
				path.Join(pth, gzfile),
 | 
						|
				path.Join(pth, jsfile),
 | 
						|
				pth)
 | 
						|
		}
 | 
						|
		if pth := os.Getenv("HOME"); pth != "" {
 | 
						|
			pth = path.Join(pth, ".tcelldb")
 | 
						|
			files = append(files,
 | 
						|
				path.Join(pth, gzhfile),
 | 
						|
				path.Join(pth, jshfile),
 | 
						|
				path.Join(pth, gzfile),
 | 
						|
				path.Join(pth, jsfile),
 | 
						|
				pth)
 | 
						|
		}
 | 
						|
 | 
						|
		for _, pth := range filepath.SplitList(gopath) {
 | 
						|
			pth = path.Join(pth, "src", "github.com",
 | 
						|
				"gdamore", "tcell", "terminfo", "database")
 | 
						|
			files = append(files,
 | 
						|
				path.Join(pth, gzhfile),
 | 
						|
				path.Join(pth, jshfile),
 | 
						|
				path.Join(pth, gzfile),
 | 
						|
				path.Join(pth, jsfile))
 | 
						|
		}
 | 
						|
 | 
						|
		for _, fname := range files {
 | 
						|
			t, _ = loadFromFile(fname, name)
 | 
						|
			if t != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if t != nil {
 | 
						|
			if t.Name != name {
 | 
						|
				// Check for a database loop (no infinite
 | 
						|
				// recursion).
 | 
						|
				dblock.Lock()
 | 
						|
				if aliases[name] != "" {
 | 
						|
					dblock.Unlock()
 | 
						|
					return nil, ErrTermNotFound
 | 
						|
				}
 | 
						|
				aliases[name] = t.Name
 | 
						|
				dblock.Unlock()
 | 
						|
				return LookupTerminfo(t.Name)
 | 
						|
			}
 | 
						|
			dblock.Lock()
 | 
						|
			terminfos[name] = t
 | 
						|
			dblock.Unlock()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If the name ends in -truecolor, then fabricate an entry
 | 
						|
	// from the corresponding -256color, -color, or bare terminal.
 | 
						|
	if t == nil && strings.HasSuffix(name, "-truecolor") {
 | 
						|
 | 
						|
		suffixes := []string{
 | 
						|
			"-256color",
 | 
						|
			"-88color",
 | 
						|
			"-color",
 | 
						|
			"",
 | 
						|
		}
 | 
						|
		base := name[:len(name)-len("-truecolor")]
 | 
						|
		for _, s := range suffixes {
 | 
						|
			if t, _ = LookupTerminfo(base + s); t != nil {
 | 
						|
				addtruecolor = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if t == nil {
 | 
						|
		return nil, ErrTermNotFound
 | 
						|
	}
 | 
						|
 | 
						|
	switch os.Getenv("TCELL_TRUECOLOR") {
 | 
						|
	case "":
 | 
						|
	case "disable":
 | 
						|
		addtruecolor = false
 | 
						|
	default:
 | 
						|
		addtruecolor = true
 | 
						|
	}
 | 
						|
 | 
						|
	// If the user has requested 24-bit color with $COLORTERM, then
 | 
						|
	// amend the value (unless already present).  This means we don't
 | 
						|
	// need to have a value present.
 | 
						|
	if addtruecolor &&
 | 
						|
		t.SetFgBgRGB == "" &&
 | 
						|
		t.SetFgRGB == "" &&
 | 
						|
		t.SetBgRGB == "" {
 | 
						|
 | 
						|
		// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
 | 
						|
		t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
 | 
						|
		t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
 | 
						|
		t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
 | 
						|
			"48;2;%p4%d;%p5%d;%p6%dm"
 | 
						|
	}
 | 
						|
 | 
						|
	return t, nil
 | 
						|
}
 |