// 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 Italic string `json:"sitm,omitempty"` // sitm Strikethrough string `json:"strikethrough,omitempty"` // strikethrough 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"` KeyCtrlPgUp string KeyCtrlPgDn string } 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 { t, _ = GetDynamic() } if t == nil { return nil, ErrTermNotFound } return t, nil }