"github.com/mitchellh/go-homedir"
"github.com/yuin/gopher-lua"
"github.com/zyedidia/clipboard"
+ "github.com/zyedidia/micro/cmd/micro/terminfo"
"github.com/zyedidia/tcell"
"github.com/zyedidia/tcell/encoding"
"layeh.com/gopher-luar"
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
+ tcelldb := os.Getenv("TCELLDB")
+ os.Setenv("TCELLDB", configDir+"/.tcelldb")
+
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
// initializing tcell, but after that, we can set the TERM back to whatever it was
oldTerm := os.Getenv("TERM")
var err error
screen, err = tcell.NewScreen()
if err != nil {
- fmt.Println(err)
if err == tcell.ErrTermNotFound {
- fmt.Println("Micro does not recognize your terminal:", oldTerm)
- fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).")
+ terminfo.WriteDB(configDir + "/.tcelldb")
+ screen, err = tcell.NewScreen()
+ if err != nil {
+ fmt.Println(err)
+ fmt.Println("Fatal: Micro could not initialize a screen.")
+ os.Exit(1)
+ }
}
- os.Exit(1)
}
if err = screen.Init(); err != nil {
fmt.Println(err)
screen.EnableMouse()
}
+ os.Setenv("TCELLDB", tcelldb)
+
// screen.SetStyle(defStyle)
}
--- /dev/null
+# Terminfo parser
+
+This terminfo parser was written by the authors of [tcell](github.com/gdamore/tcell). We are using it here
+to compile the terminal database if the terminal entry is not found in set of precompiled terminals.
+
+The source for `mkinfo.go` is adapted from tcell's `mkinfo` tool to be more of a library.
--- /dev/null
+// Copyright 2017 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.
+
+// This command is used to generate suitable configuration files in either
+// go syntax or in JSON. It defaults to JSON output on stdout. If no
+// term values are specified on the command line, then $TERM is used.
+//
+// Usage is like this:
+//
+// mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
+//
+// -gzip specifies output should be compressed (json only)
+// -go specifies Go output into the named file. Use - for stdout.
+// -json specifies JSON output in the named file. Use - for stdout
+// -nofatal indicates that errors loading definitions should not be fatal
+//
+
+package terminfo
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/zyedidia/mkinfo/terminfo"
+)
+
+type termcap struct {
+ name string
+ desc string
+ aliases []string
+ bools map[string]bool
+ nums map[string]int
+ strs map[string]string
+}
+
+func (tc *termcap) getnum(s string) int {
+ return (tc.nums[s])
+}
+
+func (tc *termcap) getflag(s string) bool {
+ return (tc.bools[s])
+}
+
+func (tc *termcap) getstr(s string) string {
+ return (tc.strs[s])
+}
+
+const (
+ NONE = iota
+ CTRL
+ ESC
+)
+
+func unescape(s string) string {
+ // Various escapes are in \x format. Control codes are
+ // encoded as ^M (carat followed by ASCII equivalent).
+ // Escapes are: \e, \E - escape
+ // \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
+ buf := &bytes.Buffer{}
+ esc := NONE
+
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ switch esc {
+ case NONE:
+ switch c {
+ case '\\':
+ esc = ESC
+ case '^':
+ esc = CTRL
+ default:
+ buf.WriteByte(c)
+ }
+ case CTRL:
+ buf.WriteByte(c - 0x40)
+ esc = NONE
+ case ESC:
+ switch c {
+ case 'E', 'e':
+ buf.WriteByte(0x1b)
+ case '0', '1', '2', '3', '4', '5', '6', '7':
+ if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
+ buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
+ i = i + 2
+ } else if c == '0' {
+ buf.WriteByte(0)
+ }
+ case 'n':
+ buf.WriteByte('\n')
+ case 'r':
+ buf.WriteByte('\r')
+ case 't':
+ buf.WriteByte('\t')
+ case 'b':
+ buf.WriteByte('\b')
+ case 'f':
+ buf.WriteByte('\f')
+ case 's':
+ buf.WriteByte(' ')
+ case 'l':
+ panic("WTF: weird format: " + s)
+ default:
+ buf.WriteByte(c)
+ }
+ esc = NONE
+ }
+ }
+ return (buf.String())
+}
+
+func (tc *termcap) setupterm(name string) error {
+ cmd := exec.Command("infocmp", "-1", name)
+ output := &bytes.Buffer{}
+ cmd.Stdout = output
+
+ tc.strs = make(map[string]string)
+ tc.bools = make(map[string]bool)
+ tc.nums = make(map[string]int)
+
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+
+ // Now parse the output.
+ // We get comment lines (starting with "#"), followed by
+ // a header line that looks like "<name>|<alias>|...|<desc>"
+ // then capabilities, one per line, starting with a tab and ending
+ // with a comma and newline.
+ lines := strings.Split(output.String(), "\n")
+ for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
+ lines = lines[1:]
+ }
+
+ // Ditch trailing empty last line
+ if lines[len(lines)-1] == "" {
+ lines = lines[:len(lines)-1]
+ }
+ header := lines[0]
+ if strings.HasSuffix(header, ",") {
+ header = header[:len(header)-1]
+ }
+ names := strings.Split(header, "|")
+ tc.name = names[0]
+ names = names[1:]
+ if len(names) > 0 {
+ tc.desc = names[len(names)-1]
+ names = names[:len(names)-1]
+ }
+ tc.aliases = names
+ for _, val := range lines[1:] {
+ if (!strings.HasPrefix(val, "\t")) ||
+ (!strings.HasSuffix(val, ",")) {
+ return (errors.New("malformed infocmp: " + val))
+ }
+
+ val = val[1:]
+ val = val[:len(val)-1]
+
+ if k := strings.SplitN(val, "=", 2); len(k) == 2 {
+ tc.strs[k[0]] = unescape(k[1])
+ } else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
+ if u, err := strconv.ParseUint(k[1], 10, 0); err != nil {
+ return (err)
+ } else {
+ tc.nums[k[0]] = int(u)
+ }
+ } else {
+ tc.bools[val] = true
+ }
+ }
+ return nil
+}
+
+// This program is used to collect data from the system's terminfo library,
+// and write it into Go source code. That is, we maintain our terminfo
+// capabilities encoded in the program. It should never need to be run by
+// an end user, but developers can use this to add codes for additional
+// terminal types.
+//
+// If a terminal name ending with -truecolor is given, and we cannot find
+// one, we will try to fabricate one from either the -256color (if present)
+// or the unadorned base name, adding the XTerm specific 24-bit color
+// escapes. We believe that all 24-bit capable terminals use the same
+// escape sequences, and terminfo has yet to evolve to support this.
+func getinfo(name string) (*terminfo.Terminfo, string, error) {
+ var tc termcap
+ addTrueColor := false
+ if err := tc.setupterm(name); err != nil {
+ if strings.HasSuffix(name, "-truecolor") {
+ base := name[:len(name)-len("-truecolor")]
+ // Probably -256color is closest to what we want
+ if err = tc.setupterm(base + "-256color"); err != nil {
+ err = tc.setupterm(base)
+ }
+ if err == nil {
+ addTrueColor = true
+ }
+ tc.name = name
+ }
+ if err != nil {
+ return nil, "", err
+ }
+ }
+ t := &terminfo.Terminfo{}
+ // If this is an alias record, then just emit the alias
+ t.Name = tc.name
+ if t.Name != name {
+ return t, "", nil
+ }
+ t.Aliases = tc.aliases
+ t.Colors = tc.getnum("colors")
+ t.Columns = tc.getnum("cols")
+ t.Lines = tc.getnum("lines")
+ t.Bell = tc.getstr("bel")
+ t.Clear = tc.getstr("clear")
+ t.EnterCA = tc.getstr("smcup")
+ t.ExitCA = tc.getstr("rmcup")
+ t.ShowCursor = tc.getstr("cnorm")
+ t.HideCursor = tc.getstr("civis")
+ t.AttrOff = tc.getstr("sgr0")
+ t.Underline = tc.getstr("smul")
+ t.Bold = tc.getstr("bold")
+ t.Blink = tc.getstr("blink")
+ t.Dim = tc.getstr("dim")
+ t.Reverse = tc.getstr("rev")
+ t.EnterKeypad = tc.getstr("smkx")
+ t.ExitKeypad = tc.getstr("rmkx")
+ t.SetFg = tc.getstr("setaf")
+ t.SetBg = tc.getstr("setab")
+ t.SetCursor = tc.getstr("cup")
+ t.CursorBack1 = tc.getstr("cub1")
+ t.CursorUp1 = tc.getstr("cuu1")
+ t.KeyF1 = tc.getstr("kf1")
+ t.KeyF2 = tc.getstr("kf2")
+ t.KeyF3 = tc.getstr("kf3")
+ t.KeyF4 = tc.getstr("kf4")
+ t.KeyF5 = tc.getstr("kf5")
+ t.KeyF6 = tc.getstr("kf6")
+ t.KeyF7 = tc.getstr("kf7")
+ t.KeyF8 = tc.getstr("kf8")
+ t.KeyF9 = tc.getstr("kf9")
+ t.KeyF10 = tc.getstr("kf10")
+ t.KeyF11 = tc.getstr("kf11")
+ t.KeyF12 = tc.getstr("kf12")
+ t.KeyF13 = tc.getstr("kf13")
+ t.KeyF14 = tc.getstr("kf14")
+ t.KeyF15 = tc.getstr("kf15")
+ t.KeyF16 = tc.getstr("kf16")
+ t.KeyF17 = tc.getstr("kf17")
+ t.KeyF18 = tc.getstr("kf18")
+ t.KeyF19 = tc.getstr("kf19")
+ t.KeyF20 = tc.getstr("kf20")
+ t.KeyF21 = tc.getstr("kf21")
+ t.KeyF22 = tc.getstr("kf22")
+ t.KeyF23 = tc.getstr("kf23")
+ t.KeyF24 = tc.getstr("kf24")
+ t.KeyF25 = tc.getstr("kf25")
+ t.KeyF26 = tc.getstr("kf26")
+ t.KeyF27 = tc.getstr("kf27")
+ t.KeyF28 = tc.getstr("kf28")
+ t.KeyF29 = tc.getstr("kf29")
+ t.KeyF30 = tc.getstr("kf30")
+ t.KeyF31 = tc.getstr("kf31")
+ t.KeyF32 = tc.getstr("kf32")
+ t.KeyF33 = tc.getstr("kf33")
+ t.KeyF34 = tc.getstr("kf34")
+ t.KeyF35 = tc.getstr("kf35")
+ t.KeyF36 = tc.getstr("kf36")
+ t.KeyF37 = tc.getstr("kf37")
+ t.KeyF38 = tc.getstr("kf38")
+ t.KeyF39 = tc.getstr("kf39")
+ t.KeyF40 = tc.getstr("kf40")
+ t.KeyF41 = tc.getstr("kf41")
+ t.KeyF42 = tc.getstr("kf42")
+ t.KeyF43 = tc.getstr("kf43")
+ t.KeyF44 = tc.getstr("kf44")
+ t.KeyF45 = tc.getstr("kf45")
+ t.KeyF46 = tc.getstr("kf46")
+ t.KeyF47 = tc.getstr("kf47")
+ t.KeyF48 = tc.getstr("kf48")
+ t.KeyF49 = tc.getstr("kf49")
+ t.KeyF50 = tc.getstr("kf50")
+ t.KeyF51 = tc.getstr("kf51")
+ t.KeyF52 = tc.getstr("kf52")
+ t.KeyF53 = tc.getstr("kf53")
+ t.KeyF54 = tc.getstr("kf54")
+ t.KeyF55 = tc.getstr("kf55")
+ t.KeyF56 = tc.getstr("kf56")
+ t.KeyF57 = tc.getstr("kf57")
+ t.KeyF58 = tc.getstr("kf58")
+ t.KeyF59 = tc.getstr("kf59")
+ t.KeyF60 = tc.getstr("kf60")
+ t.KeyF61 = tc.getstr("kf61")
+ t.KeyF62 = tc.getstr("kf62")
+ t.KeyF63 = tc.getstr("kf63")
+ t.KeyF64 = tc.getstr("kf64")
+ t.KeyInsert = tc.getstr("kich1")
+ t.KeyDelete = tc.getstr("kdch1")
+ t.KeyBackspace = tc.getstr("kbs")
+ t.KeyHome = tc.getstr("khome")
+ t.KeyEnd = tc.getstr("kend")
+ t.KeyUp = tc.getstr("kcuu1")
+ t.KeyDown = tc.getstr("kcud1")
+ t.KeyRight = tc.getstr("kcuf1")
+ t.KeyLeft = tc.getstr("kcub1")
+ t.KeyPgDn = tc.getstr("knp")
+ t.KeyPgUp = tc.getstr("kpp")
+ t.KeyBacktab = tc.getstr("kcbt")
+ t.KeyExit = tc.getstr("kext")
+ t.KeyCancel = tc.getstr("kcan")
+ t.KeyPrint = tc.getstr("kprt")
+ t.KeyHelp = tc.getstr("khlp")
+ t.KeyClear = tc.getstr("kclr")
+ t.AltChars = tc.getstr("acsc")
+ t.EnterAcs = tc.getstr("smacs")
+ t.ExitAcs = tc.getstr("rmacs")
+ t.EnableAcs = tc.getstr("enacs")
+ t.Mouse = tc.getstr("kmous")
+ t.KeyShfRight = tc.getstr("kRIT")
+ t.KeyShfLeft = tc.getstr("kLFT")
+ t.KeyShfHome = tc.getstr("kHOM")
+ t.KeyShfEnd = tc.getstr("kEND")
+
+ // Terminfo lacks descriptions for a bunch of modified keys,
+ // but modern XTerm and emulators often have them. Let's add them,
+ // if the shifted right and left arrows are defined.
+ if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
+ t.KeyShfUp = "\x1b[1;2A"
+ t.KeyShfDown = "\x1b[1;2B"
+ t.KeyMetaUp = "\x1b[1;9A"
+ t.KeyMetaDown = "\x1b[1;9B"
+ t.KeyMetaRight = "\x1b[1;9C"
+ t.KeyMetaLeft = "\x1b[1;9D"
+ t.KeyAltUp = "\x1b[1;3A"
+ t.KeyAltDown = "\x1b[1;3B"
+ t.KeyAltRight = "\x1b[1;3C"
+ t.KeyAltLeft = "\x1b[1;3D"
+ t.KeyCtrlUp = "\x1b[1;5A"
+ t.KeyCtrlDown = "\x1b[1;5B"
+ t.KeyCtrlRight = "\x1b[1;5C"
+ t.KeyCtrlLeft = "\x1b[1;5D"
+ t.KeyAltShfUp = "\x1b[1;4A"
+ t.KeyAltShfDown = "\x1b[1;4B"
+ t.KeyAltShfRight = "\x1b[1;4C"
+ t.KeyAltShfLeft = "\x1b[1;4D"
+
+ t.KeyMetaShfUp = "\x1b[1;10A"
+ t.KeyMetaShfDown = "\x1b[1;10B"
+ t.KeyMetaShfRight = "\x1b[1;10C"
+ t.KeyMetaShfLeft = "\x1b[1;10D"
+
+ t.KeyCtrlShfUp = "\x1b[1;6A"
+ t.KeyCtrlShfDown = "\x1b[1;6B"
+ t.KeyCtrlShfRight = "\x1b[1;6C"
+ t.KeyCtrlShfLeft = "\x1b[1;6D"
+ }
+ // And also for Home and End
+ if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
+ t.KeyCtrlHome = "\x1b[1;5H"
+ t.KeyCtrlEnd = "\x1b[1;5F"
+ t.KeyAltHome = "\x1b[1;9H"
+ t.KeyAltEnd = "\x1b[1;9F"
+ t.KeyCtrlShfHome = "\x1b[1;6H"
+ t.KeyCtrlShfEnd = "\x1b[1;6F"
+ t.KeyAltShfHome = "\x1b[1;4H"
+ t.KeyAltShfEnd = "\x1b[1;4F"
+ t.KeyMetaShfHome = "\x1b[1;10H"
+ t.KeyMetaShfEnd = "\x1b[1;10F"
+ }
+
+ // And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
+ // It seems that urxvt at least send ESC as ALT prefix for these,
+ // although some places seem to indicate a separate ALT key sesquence.
+ if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
+ t.KeyShfUp = "\x1b[a"
+ t.KeyShfDown = "\x1b[b"
+ t.KeyCtrlUp = "\x1b[Oa"
+ t.KeyCtrlDown = "\x1b[Ob"
+ t.KeyCtrlRight = "\x1b[Oc"
+ t.KeyCtrlLeft = "\x1b[Od"
+ }
+ if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
+ t.KeyCtrlHome = "\x1b[7^"
+ t.KeyCtrlEnd = "\x1b[8^"
+ }
+
+ // If the kmous entry is present, then we need to record the
+ // the codes to enter and exit mouse mode. Sadly, this is not
+ // part of the terminfo databases anywhere that I've found, but
+ // is an extension. The escape codes are documented in the XTerm
+ // manual, and all terminals that have kmous are expected to
+ // use these same codes, unless explicitly configured otherwise
+ // vi XM. Note that in any event, we only known how to parse either
+ // x11 or SGR mouse events -- if your terminal doesn't support one
+ // of these two forms, you maybe out of luck.
+ t.MouseMode = tc.getstr("XM")
+ if t.Mouse != "" && t.MouseMode == "" {
+ // we anticipate that all xterm mouse tracking compatible
+ // terminals understand mouse tracking (1000), but we hope
+ // that those that don't understand any-event tracking (1003)
+ // will at least ignore it. Likewise we hope that terminals
+ // that don't understand SGR reporting (1006) just ignore it.
+ t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
+ "\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
+ }
+
+ // We only support colors in ANSI 8 or 256 color mode.
+ if t.Colors < 8 || t.SetFg == "" {
+ t.Colors = 0
+ }
+ if t.SetCursor == "" {
+ return nil, "", errors.New("terminal not cursor addressable")
+ }
+
+ // For padding, we lookup the pad char. If that isn't present,
+ // and npc is *not* set, then we assume a null byte.
+ t.PadChar = tc.getstr("pad")
+ if t.PadChar == "" {
+ if !tc.getflag("npc") {
+ t.PadChar = "\u0000"
+ }
+ }
+
+ // For some terminals we fabricate a -truecolor entry, that may
+ // not exist in terminfo.
+ if addTrueColor {
+ 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"
+ }
+
+ // For terminals that use "standard" SGR sequences, lets combine the
+ // foreground and background together.
+ if strings.HasPrefix(t.SetFg, "\x1b[") &&
+ strings.HasPrefix(t.SetBg, "\x1b[") &&
+ strings.HasSuffix(t.SetFg, "m") &&
+ strings.HasSuffix(t.SetBg, "m") {
+ fg := t.SetFg[:len(t.SetFg)-1]
+ r := regexp.MustCompile("%p1")
+ bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
+ t.SetFgBg = fg + ";" + bg
+ }
+
+ return t, tc.desc, nil
+}
+
+func WriteDB(filename string) error {
+ var e error
+ js := []byte{}
+ args := []string{os.Getenv("TERM")}
+
+ tdata := make(map[string]*terminfo.Terminfo)
+ descs := make(map[string]string)
+
+ for _, term := range args {
+ if t, desc, e := getinfo(term); e != nil {
+ return e
+ } else {
+ tdata[term] = t
+ descs[term] = desc
+ }
+ }
+
+ if len(tdata) == 0 {
+ // No data.
+ return errors.New("No data")
+ }
+ o := os.Stdout
+ if o, e = os.Create(filename); e != nil {
+ return e
+ }
+ var w io.WriteCloser
+ w = o
+ for _, term := range args {
+ if t := tdata[term]; t != nil {
+ js, e = json.Marshal(t)
+ fmt.Fprintln(w, string(js))
+ }
+ // arguably if there is more than one term, this
+ // should be a javascript array, but that's not how
+ // we load it. We marshal objects one at a time from
+ // the file.
+ }
+ if e != nil {
+ return e
+ }
+ w.Close()
+ if w != o {
+ o.Close()
+ }
+
+ return nil
+}
--- /dev/null
+// Copyright 2017 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"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "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 in this package's source directory as database.json).
+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)
+
+ // 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 looks like:
+ //
+ // $TCELLDB/x/xterm.gz
+ // $TCELLDB/x/xterm
+ // $TCELLDB
+ // $HOME/.tcelldb/x/xterm.gz
+ // $HOME/.tcelldb/x/xterm
+ // $HOME/.tcelldb
+ // $GOPATH/terminfo/database/x/xterm.gz
+ // $GOPATH/terminfo/database/x/xterm
+ //
+ if pth := os.Getenv("TCELLDB"); pth != "" {
+ files = append(files, path.Join(pth, gzfile))
+ files = append(files, path.Join(pth, jsfile))
+ files = append(files, pth)
+ }
+ if pth := os.Getenv("HOME"); pth != "" {
+ pth = path.Join(pth, ".tcelldb")
+ files = append(files, path.Join(pth, gzfile))
+ files = append(files, path.Join(pth, jsfile))
+ files = append(files, pth)
+ }
+
+ for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
+ pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database")
+ files = append(files, path.Join(pth, gzfile))
+ files = append(files, 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
+}
--- /dev/null
+// Copyright 2016 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"
+ "os"
+ "testing"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+// This terminfo entry is a stripped down version from
+// xterm-256color, but I've added some of my own entries.
+var testTerminfo = &Terminfo{
+ Name: "simulation_test",
+ Columns: 80,
+ Lines: 24,
+ Colors: 256,
+ Bell: "\a",
+ Blink: "\x1b2ms$<2>",
+ Reverse: "\x1b[7m",
+ SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
+ SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
+ AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
+ Mouse: "\x1b[M",
+ MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
+ SetCursor: "\x1b[%i%p1%d;%p2%dH",
+ PadChar: "\x00",
+}
+
+func TestTerminfo(t *testing.T) {
+
+ ti := testTerminfo
+
+ Convey("Terminfo parameter processing", t, func() {
+ // This tests %i, and basic parameter strings too
+ Convey("TGoto works", func() {
+ s := ti.TGoto(7, 9)
+ So(s, ShouldEqual, "\x1b[10;8H")
+ })
+
+ // This tests some conditionals
+ Convey("TParm extended formats work", func() {
+ s := ti.TParm("A[%p1%2.2X]B", 47)
+ So(s, ShouldEqual, "A[2F]B")
+ })
+
+ // This tests some conditionals
+ Convey("TParm colors work", func() {
+ s := ti.TParm(ti.SetFg, 7)
+ So(s, ShouldEqual, "\x1b[37m")
+
+ s = ti.TParm(ti.SetFg, 15)
+ So(s, ShouldEqual, "\x1b[97m")
+
+ s = ti.TParm(ti.SetFg, 200)
+ So(s, ShouldEqual, "\x1b[38;5;200m")
+ })
+
+ // This tests variables
+ Convey("TParm mouse mode works", func() {
+ s := ti.TParm(ti.MouseMode, 1)
+ So(s, ShouldEqual, "\x1b[?1000h\x1b[?1003h\x1b[?1006h")
+ s = ti.TParm(ti.MouseMode, 0)
+ So(s, ShouldEqual, "\x1b[?1000l\x1b[?1003l\x1b[?1006l")
+ })
+
+ })
+
+ Convey("Terminfo delay handling", t, func() {
+
+ Convey("19200 baud", func() {
+ buf := bytes.NewBuffer(nil)
+ ti.TPuts(buf, ti.Blink, 19200)
+ s := string(buf.Bytes())
+ So(s, ShouldEqual, "\x1b2ms\x00\x00\x00\x00")
+ })
+
+ Convey("50 baud", func() {
+ buf := bytes.NewBuffer(nil)
+ ti.TPuts(buf, ti.Blink, 50)
+ s := string(buf.Bytes())
+ So(s, ShouldEqual, "\x1b2ms")
+ })
+ })
+}
+
+func TestTerminfoDatabase(t *testing.T) {
+
+ Convey("Database Lookups work", t, func() {
+ Convey("Basic lookup works", func() {
+ os.Setenv("TCELLDB", "testdata/test1")
+ ti, err := LookupTerminfo("test1")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Columns, ShouldEqual, 80)
+
+ ti, err = LookupTerminfo("alias1")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Columns, ShouldEqual, 80)
+
+ os.Setenv("TCELLDB", "testdata")
+ ti, err = LookupTerminfo("test2")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Columns, ShouldEqual, 80)
+ So(len(ti.Aliases), ShouldEqual, 1)
+ So(ti.Aliases[0], ShouldEqual, "alias2")
+ })
+
+ Convey("Incorrect primary name works", func() {
+ os.Setenv("TCELLDB", "testdata")
+ ti, err := LookupTerminfo("test3")
+ So(err, ShouldNotBeNil)
+ So(ti, ShouldBeNil)
+ })
+
+ Convey("Loops fail", func() {
+ os.Setenv("TCELLDB", "testdata")
+ ti, err := LookupTerminfo("loop1")
+ So(ti, ShouldBeNil)
+ So(err, ShouldNotBeNil)
+ })
+
+ Convey("Gzip database works", func() {
+ os.Setenv("TCELLDB", "testdata")
+ ti, err := LookupTerminfo("test-gzip")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Columns, ShouldEqual, 80)
+ })
+
+ Convey("Gzip alias lookup works", func() {
+ os.Setenv("TCELLDB", "testdata")
+ ti, err := LookupTerminfo("alias-gzip")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Columns, ShouldEqual, 80)
+ })
+
+ Convey("Broken alias works", func() {
+ os.Setenv("TCELLDB", "testdata")
+ ti, err := LookupTerminfo("alias-none")
+ So(err, ShouldNotBeNil)
+ So(ti, ShouldBeNil)
+ })
+
+ Convey("Combined database works", func() {
+ os.Setenv("TCELLDB", "testdata/combined")
+ ti, err := LookupTerminfo("combined2")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Lines, ShouldEqual, 102)
+
+ ti, err = LookupTerminfo("alias-comb1")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Lines, ShouldEqual, 101)
+
+ ti, err = LookupTerminfo("combined3")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Lines, ShouldEqual, 103)
+
+ ti, err = LookupTerminfo("combined1")
+ So(err, ShouldBeNil)
+ So(ti, ShouldNotBeNil)
+ So(ti.Lines, ShouldEqual, 101)
+ })
+ })
+}
+
+func BenchmarkSetFgBg(b *testing.B) {
+ ti := testTerminfo
+
+ for i := 0; i < b.N; i++ {
+ ti.TParm(ti.SetFg, 100, 200)
+ ti.TParm(ti.SetBg, 100, 200)
+ }
+}