mirror of
https://github.com/gdamore/tcell.git
synced 2025-04-26 13:48:53 +08:00

This changes the database to use sha1 based file names. Its not beautiful, but this is the BS we have to do to cope with the garbage that is case insensitive filesystems. Legacy databases are still honored, if you have them.
875 lines
25 KiB
Go
875 lines
25 KiB
Go
// Copyright 2018 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
|
|
}
|
|
|
|
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 t == nil {
|
|
return nil, ErrTermNotFound
|
|
}
|
|
return t, nil
|
|
}
|