mirror of https://github.com/gdamore/tcell.git
645 lines
18 KiB
Go
645 lines
18 KiB
Go
// Copyright 2015 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 tcell
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// 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
|
|
SetFgRGB string `json:"setfrgb,omitempty"` // setfrgb, non-standard
|
|
SetBgRGB string `json:"setbrgb,omitempty"` // setbrgb, non-standard
|
|
}
|
|
|
|
type stack []string
|
|
|
|
func (st stack) Push(v string) stack {
|
|
return append(st, v)
|
|
}
|
|
|
|
func (st stack) Pop() (string, stack) {
|
|
v := ""
|
|
if len(st) > 0 {
|
|
v = st[len(st)-1]
|
|
st = st[:len(st)-1]
|
|
}
|
|
return v, st
|
|
}
|
|
|
|
func (st stack) PopInt() (int, stack) {
|
|
v := ""
|
|
v, st = st.Pop()
|
|
i, _ := strconv.Atoi(v)
|
|
return i, st
|
|
}
|
|
|
|
func (st stack) PopBool() (bool, stack) {
|
|
v := ""
|
|
v, st = st.Pop()
|
|
if v == "1" {
|
|
return true, st
|
|
}
|
|
return false, st
|
|
}
|
|
|
|
func (st stack) PushInt(i int) stack {
|
|
return st.Push(strconv.Itoa(i))
|
|
}
|
|
|
|
func (st stack) PushBool(i bool) stack {
|
|
if i {
|
|
return st.Push("1")
|
|
}
|
|
return st.Push("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
|
|
|
|
// 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
|
|
|
|
buf := bytes.NewBufferString(s)
|
|
out := &bytes.Buffer{}
|
|
|
|
// 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 := buf.ReadByte()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if ch != '%' {
|
|
out.WriteByte(ch)
|
|
continue
|
|
}
|
|
|
|
ch, err = buf.ReadByte()
|
|
if err != nil {
|
|
// XXX Error
|
|
break
|
|
}
|
|
|
|
switch ch {
|
|
case '%': // quoted %
|
|
out.WriteByte(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()
|
|
out.WriteString(a)
|
|
|
|
case 'd':
|
|
ai, stk = stk.PopInt()
|
|
out.WriteString(strconv.Itoa(ai))
|
|
|
|
case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
|
|
f := "%"
|
|
if ch == ':' {
|
|
ch, _ = buf.ReadByte()
|
|
}
|
|
f += string(ch)
|
|
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
|
|
f += string(ch)
|
|
}
|
|
for (ch >= '0' && ch <= '9') || ch == '.' {
|
|
ch, _ = buf.ReadByte()
|
|
f += string(ch)
|
|
}
|
|
switch ch {
|
|
case 'd', 'x', 'X', 'o':
|
|
ai, stk = stk.PopInt()
|
|
out.WriteString(fmt.Sprintf(f, ai))
|
|
case 'c', 's':
|
|
a, stk = stk.Pop()
|
|
out.WriteString(fmt.Sprintf(f, a))
|
|
}
|
|
|
|
case 'p': // push parameter
|
|
ch, _ = buf.ReadByte()
|
|
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, _ = buf.ReadByte()
|
|
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, _ = buf.ReadByte()
|
|
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, _ = buf.ReadByte()
|
|
buf.ReadByte() // must be ' but we don't check
|
|
stk = stk.Push(string(ch))
|
|
|
|
case '{': // push(int)
|
|
ai = 0
|
|
ch, _ = buf.ReadByte()
|
|
for ch >= '0' && ch <= '9' {
|
|
ai *= 10
|
|
ai += int(ch - '0')
|
|
ch, _ = buf.ReadByte()
|
|
}
|
|
// 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 = buf.ReadByte()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if ch != '%' {
|
|
continue
|
|
}
|
|
ch, _ = buf.ReadByte()
|
|
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 = buf.ReadByte()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if ch != '%' {
|
|
continue
|
|
}
|
|
ch, _ = buf.ReadByte()
|
|
switch ch {
|
|
case ';':
|
|
if nest == 0 {
|
|
break elloop
|
|
}
|
|
nest--
|
|
case '?':
|
|
nest++
|
|
}
|
|
}
|
|
|
|
case ';': // endif
|
|
|
|
}
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
// 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(fg, bg Color) string {
|
|
fi := int(fg)
|
|
bi := int(bg)
|
|
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 terminfos map[string]*Terminfo
|
|
var aliases map[string]string
|
|
var dblock sync.Mutex
|
|
|
|
func initDB() {
|
|
if terminfos == nil {
|
|
terminfos = make(map[string]*Terminfo)
|
|
}
|
|
if aliases == nil {
|
|
aliases = make(map[string]string)
|
|
}
|
|
}
|
|
|
|
// AddTerminfo can be called to register a new Terminfo entry.
|
|
func AddTerminfo(t *Terminfo) {
|
|
dblock.Lock()
|
|
initDB()
|
|
terminfos[t.Name] = t
|
|
for _, x := range t.Aliases {
|
|
terminfos[x] = t
|
|
}
|
|
dblock.Unlock()
|
|
}
|
|
|
|
func loadFromFile(fname string, term string) (*Terminfo, error) {
|
|
f, e := os.Open(fname)
|
|
if 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.Name == term {
|
|
return t, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// LookupTerminfo attemps 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 in this package's source directory as database.json).
|
|
func LookupTerminfo(name string) (*Terminfo, error) {
|
|
dblock.Lock()
|
|
initDB()
|
|
t := terminfos[name]
|
|
dblock.Unlock()
|
|
|
|
if t == nil {
|
|
// Load the database located here. Its expected that TCELLSDB
|
|
// points either to a single JSON file.
|
|
if pth := os.Getenv("TCELLDB"); pth != "" {
|
|
t, _ = loadFromFile(pth, name)
|
|
}
|
|
if t == nil {
|
|
if pth := os.Getenv("HOME"); pth != "" {
|
|
fname := path.Join(pth, ".tcelldb")
|
|
t, _ = loadFromFile(fname, name)
|
|
}
|
|
}
|
|
if t == nil {
|
|
gopath := strings.Split(os.Getenv("GOPATH"),
|
|
string(os.PathListSeparator))
|
|
fname := path.Join("src",
|
|
"github.com", "gdamore", "tcell",
|
|
"database.json")
|
|
for _, pth := range gopath {
|
|
t, _ = loadFromFile(path.Join(pth, fname), name)
|
|
if t != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if t != nil {
|
|
dblock.Lock()
|
|
terminfos[name] = t
|
|
dblock.Unlock()
|
|
}
|
|
}
|
|
if t == nil {
|
|
return nil, ErrTermNotFound
|
|
}
|
|
return t, nil
|
|
}
|