From 64fa922ec013079f8f0c90fc9e93c56db3611d30 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 22 Apr 2018 21:25:06 +0300 Subject: Switch to dep --- vendor/maunium.net/go/tcell/tscreen.go | 1524 ++++++++++++++++++++++++++++++++ 1 file changed, 1524 insertions(+) create mode 100644 vendor/maunium.net/go/tcell/tscreen.go (limited to 'vendor/maunium.net/go/tcell/tscreen.go') diff --git a/vendor/maunium.net/go/tcell/tscreen.go b/vendor/maunium.net/go/tcell/tscreen.go new file mode 100644 index 0000000..d8e62b2 --- /dev/null +++ b/vendor/maunium.net/go/tcell/tscreen.go @@ -0,0 +1,1524 @@ +// 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 tcell + +import ( + "bytes" + "io" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" + + "golang.org/x/text/transform" + + "maunium.net/go/tcell/terminfo" +) + +// NewTerminfoScreen returns a Screen that uses the stock TTY interface +// and POSIX termios, combined with a terminfo description taken from +// the $TERM environment variable. It returns an error if the terminal +// is not supported for any reason. +// +// For terminals that do not support dynamic resize events, the $LINES +// $COLUMNS environment variables can be set to the actual window size, +// otherwise defaults taken from the terminal database are used. +func NewTerminfoScreen() (Screen, error) { + ti, e := terminfo.LookupTerminfo(os.Getenv("TERM")) + if e != nil { + return nil, e + } + t := &tScreen{ti: ti} + + t.keyexist = make(map[Key]bool) + t.keycodes = make(map[string]*tKeyCode) + if len(ti.Mouse) > 0 { + t.mouse = []byte(ti.Mouse) + } + t.prepareKeys() + t.buildAcsMap() + t.sigwinch = make(chan os.Signal, 10) + t.fallback = make(map[rune]string) + for k, v := range RuneFallbacks { + t.fallback[k] = v + } + + return t, nil +} + +// tKeyCode represents a combination of a key code and modifiers. +type tKeyCode struct { + key Key + mod ModMask +} + +// tScreen represents a screen backed by a terminfo implementation. +type tScreen struct { + ti *terminfo.Terminfo + h int + w int + fini bool + cells CellBuffer + in *os.File + out io.Writer + curstyle Style + style Style + evch chan Event + sigwinch chan os.Signal + quit chan struct{} + indoneq chan struct{} + keyexist map[Key]bool + keycodes map[string]*tKeyCode + keychan chan []byte + keytimer *time.Timer + keyexpire time.Time + cx int + cy int + mouse []byte + clear bool + cursorx int + cursory int + tiosp *termiosPrivate + baud int + wasbtn bool + acs map[rune]string + charset string + encoder transform.Transformer + decoder transform.Transformer + fallback map[rune]string + colors map[Color]Color + palette []Color + truecolor bool + escaped bool + buttondn bool + hasSetTitle bool + + sync.Mutex +} + +func (t *tScreen) Init() error { + t.evch = make(chan Event, 10) + t.indoneq = make(chan struct{}) + t.keychan = make(chan []byte, 10) + t.keytimer = time.NewTimer(time.Millisecond * 50) + t.charset = "UTF-8" + + t.charset = getCharset() + if enc := GetEncoding(t.charset); enc != nil { + t.encoder = enc.NewEncoder() + t.decoder = enc.NewDecoder() + } else { + return ErrNoCharset + } + ti := t.ti + + // environment overrides + w := ti.Columns + h := ti.Lines + if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 { + h = i + } + if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 { + w = i + } + if e := t.termioInit(); e != nil { + return e + } + + if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" { + t.truecolor = true + } + // A user who wants to have his themes honored can + // set this environment variable. + if os.Getenv("TCELL_TRUECOLOR") == "disable" { + t.truecolor = false + } + if !t.truecolor { + t.colors = make(map[Color]Color) + t.palette = make([]Color, t.Colors()) + for i := 0; i < t.Colors(); i++ { + t.palette[i] = Color(i) + // identity map for our builtin colors + t.colors[Color(i)] = Color(i) + } + } + + t.TPuts(ti.EnterCA) + t.TPuts(ti.HideCursor) + t.TPuts(ti.EnableAcs) + t.TPuts(ti.Clear) + t.TPuts("\x1b[?2004h") + + t.quit = make(chan struct{}) + + t.Lock() + t.cx = -1 + t.cy = -1 + t.style = StyleDefault + t.cells.Resize(w, h) + t.cursorx = -1 + t.cursory = -1 + t.resize() + t.Unlock() + + go t.mainLoop() + go t.inputLoop() + + return nil +} + +func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) { + if val != "" { + // Do not overrride codes that already exist + if _, exist := t.keycodes[val]; !exist { + t.keyexist[key] = true + t.keycodes[val] = &tKeyCode{key: key, mod: mod} + } + } +} + +func (t *tScreen) prepareKey(key Key, val string) { + t.prepareKeyMod(key, ModNone, val) +} + +func (t *tScreen) prepareKeys() { + ti := t.ti + t.prepareKey(KeyBackspace, ti.KeyBackspace) + t.prepareKey(KeyF1, ti.KeyF1) + t.prepareKey(KeyF2, ti.KeyF2) + t.prepareKey(KeyF3, ti.KeyF3) + t.prepareKey(KeyF4, ti.KeyF4) + t.prepareKey(KeyF5, ti.KeyF5) + t.prepareKey(KeyF6, ti.KeyF6) + t.prepareKey(KeyF7, ti.KeyF7) + t.prepareKey(KeyF8, ti.KeyF8) + t.prepareKey(KeyF9, ti.KeyF9) + t.prepareKey(KeyF10, ti.KeyF10) + t.prepareKey(KeyF11, ti.KeyF11) + t.prepareKey(KeyF12, ti.KeyF12) + t.prepareKey(KeyF13, ti.KeyF13) + t.prepareKey(KeyF14, ti.KeyF14) + t.prepareKey(KeyF15, ti.KeyF15) + t.prepareKey(KeyF16, ti.KeyF16) + t.prepareKey(KeyF17, ti.KeyF17) + t.prepareKey(KeyF18, ti.KeyF18) + t.prepareKey(KeyF19, ti.KeyF19) + t.prepareKey(KeyF20, ti.KeyF20) + t.prepareKey(KeyF21, ti.KeyF21) + t.prepareKey(KeyF22, ti.KeyF22) + t.prepareKey(KeyF23, ti.KeyF23) + t.prepareKey(KeyF24, ti.KeyF24) + t.prepareKey(KeyF25, ti.KeyF25) + t.prepareKey(KeyF26, ti.KeyF26) + t.prepareKey(KeyF27, ti.KeyF27) + t.prepareKey(KeyF28, ti.KeyF28) + t.prepareKey(KeyF29, ti.KeyF29) + t.prepareKey(KeyF30, ti.KeyF30) + t.prepareKey(KeyF31, ti.KeyF31) + t.prepareKey(KeyF32, ti.KeyF32) + t.prepareKey(KeyF33, ti.KeyF33) + t.prepareKey(KeyF34, ti.KeyF34) + t.prepareKey(KeyF35, ti.KeyF35) + t.prepareKey(KeyF36, ti.KeyF36) + t.prepareKey(KeyF37, ti.KeyF37) + t.prepareKey(KeyF38, ti.KeyF38) + t.prepareKey(KeyF39, ti.KeyF39) + t.prepareKey(KeyF40, ti.KeyF40) + t.prepareKey(KeyF41, ti.KeyF41) + t.prepareKey(KeyF42, ti.KeyF42) + t.prepareKey(KeyF43, ti.KeyF43) + t.prepareKey(KeyF44, ti.KeyF44) + t.prepareKey(KeyF45, ti.KeyF45) + t.prepareKey(KeyF46, ti.KeyF46) + t.prepareKey(KeyF47, ti.KeyF47) + t.prepareKey(KeyF48, ti.KeyF48) + t.prepareKey(KeyF49, ti.KeyF49) + t.prepareKey(KeyF50, ti.KeyF50) + t.prepareKey(KeyF51, ti.KeyF51) + t.prepareKey(KeyF52, ti.KeyF52) + t.prepareKey(KeyF53, ti.KeyF53) + t.prepareKey(KeyF54, ti.KeyF54) + t.prepareKey(KeyF55, ti.KeyF55) + t.prepareKey(KeyF56, ti.KeyF56) + t.prepareKey(KeyF57, ti.KeyF57) + t.prepareKey(KeyF58, ti.KeyF58) + t.prepareKey(KeyF59, ti.KeyF59) + t.prepareKey(KeyF60, ti.KeyF60) + t.prepareKey(KeyF61, ti.KeyF61) + t.prepareKey(KeyF62, ti.KeyF62) + t.prepareKey(KeyF63, ti.KeyF63) + t.prepareKey(KeyF64, ti.KeyF64) + t.prepareKey(KeyInsert, ti.KeyInsert) + t.prepareKey(KeyDelete, ti.KeyDelete) + t.prepareKey(KeyHome, ti.KeyHome) + t.prepareKey(KeyEnd, ti.KeyEnd) + t.prepareKey(KeyUp, ti.KeyUp) + t.prepareKey(KeyDown, ti.KeyDown) + t.prepareKey(KeyLeft, ti.KeyLeft) + t.prepareKey(KeyRight, ti.KeyRight) + t.prepareKey(KeyPgUp, ti.KeyPgUp) + t.prepareKey(KeyPgDn, ti.KeyPgDn) + t.prepareKey(KeyHelp, ti.KeyHelp) + t.prepareKey(KeyPrint, ti.KeyPrint) + t.prepareKey(KeyCancel, ti.KeyCancel) + t.prepareKey(KeyExit, ti.KeyExit) + t.prepareKey(KeyBacktab, ti.KeyBacktab) + + t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight) + t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft) + t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp) + t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown) + t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome) + t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd) + + t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight) + t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft) + t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp) + t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown) + t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome) + t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd) + + t.prepareKeyMod(KeyRight, ModAlt, ti.KeyAltRight) + t.prepareKeyMod(KeyLeft, ModAlt, ti.KeyAltLeft) + t.prepareKeyMod(KeyUp, ModAlt, ti.KeyAltUp) + t.prepareKeyMod(KeyDown, ModAlt, ti.KeyAltDown) + t.prepareKeyMod(KeyHome, ModAlt, ti.KeyAltHome) + t.prepareKeyMod(KeyEnd, ModAlt, ti.KeyAltEnd) + + t.prepareKeyMod(KeyRight, ModAlt, ti.KeyMetaRight) + t.prepareKeyMod(KeyLeft, ModAlt, ti.KeyMetaLeft) + t.prepareKeyMod(KeyUp, ModAlt, ti.KeyMetaUp) + t.prepareKeyMod(KeyDown, ModAlt, ti.KeyMetaDown) + t.prepareKeyMod(KeyHome, ModAlt, ti.KeyMetaHome) + t.prepareKeyMod(KeyEnd, ModAlt, ti.KeyMetaEnd) + + t.prepareKeyMod(KeyRight, ModAlt|ModShift, ti.KeyAltShfRight) + t.prepareKeyMod(KeyLeft, ModAlt|ModShift, ti.KeyAltShfLeft) + t.prepareKeyMod(KeyUp, ModAlt|ModShift, ti.KeyAltShfUp) + t.prepareKeyMod(KeyDown, ModAlt|ModShift, ti.KeyAltShfDown) + t.prepareKeyMod(KeyHome, ModAlt|ModShift, ti.KeyAltShfHome) + t.prepareKeyMod(KeyEnd, ModAlt|ModShift, ti.KeyAltShfEnd) + + t.prepareKeyMod(KeyRight, ModAlt|ModShift, ti.KeyMetaShfRight) + t.prepareKeyMod(KeyLeft, ModAlt|ModShift, ti.KeyMetaShfLeft) + t.prepareKeyMod(KeyUp, ModAlt|ModShift, ti.KeyMetaShfUp) + t.prepareKeyMod(KeyDown, ModAlt|ModShift, ti.KeyMetaShfDown) + t.prepareKeyMod(KeyHome, ModAlt|ModShift, ti.KeyMetaShfHome) + t.prepareKeyMod(KeyEnd, ModAlt|ModShift, ti.KeyMetaShfEnd) + + t.prepareKeyMod(KeyRight, ModCtrl|ModShift, ti.KeyCtrlShfRight) + t.prepareKeyMod(KeyLeft, ModCtrl|ModShift, ti.KeyCtrlShfLeft) + t.prepareKeyMod(KeyUp, ModCtrl|ModShift, ti.KeyCtrlShfUp) + t.prepareKeyMod(KeyDown, ModCtrl|ModShift, ti.KeyCtrlShfDown) + t.prepareKeyMod(KeyHome, ModCtrl|ModShift, ti.KeyCtrlShfHome) + t.prepareKeyMod(KeyEnd, ModCtrl|ModShift, ti.KeyCtrlShfEnd) + + // Sadly, xterm handling of keycodes is somewhat erratic. In + // particular, different codes are sent depending on application + // mode is in use or not, and the entries for many of these are + // simply absent from terminfo on many systems. So we insert + // a number of escape sequences if they are not already used, in + // order to have the widest correct usage. Note that prepareKey + // will not inject codes if the escape sequence is already known. + // We also only do this for terminals that have the application + // mode present. + + // Cursor mode + if ti.EnterKeypad != "" { + t.prepareKey(KeyUp, "\x1b[A") + t.prepareKey(KeyDown, "\x1b[B") + t.prepareKey(KeyRight, "\x1b[C") + t.prepareKey(KeyLeft, "\x1b[D") + t.prepareKey(KeyEnd, "\x1b[F") + t.prepareKey(KeyHome, "\x1b[H") + t.prepareKey(KeyDelete, "\x1b[3~") + t.prepareKey(KeyHome, "\x1b[1~") + t.prepareKey(KeyEnd, "\x1b[4~") + t.prepareKey(KeyPgUp, "\x1b[5~") + t.prepareKey(KeyPgDn, "\x1b[6~") + + // Application mode + t.prepareKey(KeyUp, "\x1bOA") + t.prepareKey(KeyDown, "\x1bOB") + t.prepareKey(KeyRight, "\x1bOC") + t.prepareKey(KeyLeft, "\x1bOD") + t.prepareKey(KeyHome, "\x1bOH") + + // Extra arrow key commands + t.prepareKey(KeyAltUp, "\x1b[1;9A") + t.prepareKey(KeyAltDown, "\x1b[1;9B") + t.prepareKey(KeyAltLeft, "\x1b[1;9D") + t.prepareKey(KeyAltRight, "\x1b[1;9C") + t.prepareKey(KeyAltUp, "\x1b\x1b[A") + t.prepareKey(KeyAltDown, "\x1b\x1b[B") + t.prepareKey(KeyAltLeft, "\x1b\x1b[D") + t.prepareKey(KeyAltRight, "\x1b\x1b[C") + t.prepareKey(KeyAltUp, "\x1b[1;3A") + t.prepareKey(KeyAltDown, "\x1b[1;3B") + t.prepareKey(KeyAltLeft, "\x1b[1;3D") + t.prepareKey(KeyAltRight, "\x1b[1;3C") + t.prepareKey(KeyShiftUp, "\x1b[1;2A") + t.prepareKey(KeyShiftDown, "\x1b[1;2B") + t.prepareKey(KeyShiftLeft, "\x1b[1;2D") + t.prepareKey(KeyShiftRight, "\x1b[1;2C") + t.prepareKey(KeyCtrlUp, "\x1b[1;5A") + t.prepareKey(KeyCtrlDown, "\x1b[1;5B") + t.prepareKey(KeyCtrlLeft, "\x1b[1;5D") + t.prepareKey(KeyCtrlRight, "\x1b[1;5C") + t.prepareKey(KeyAltShiftUp, "\x1b[1;10A") + t.prepareKey(KeyAltShiftDown, "\x1b[1;10B") + t.prepareKey(KeyAltShiftLeft, "\x1b[1;10D") + t.prepareKey(KeyAltShiftRight, "\x1b[1;10C") + t.prepareKey(KeyAltShiftUp, "\x1b[1;4A") + t.prepareKey(KeyAltShiftDown, "\x1b[1;4B") + t.prepareKey(KeyAltShiftLeft, "\x1b[1;4D") + t.prepareKey(KeyAltShiftRight, "\x1b[1;4C") + t.prepareKey(KeyCtrlShiftUp, "\x1b[1;6A") + t.prepareKey(KeyCtrlShiftDown, "\x1b[1;6B") + t.prepareKey(KeyCtrlShiftLeft, "\x1b[1;6D") + t.prepareKey(KeyCtrlShiftRight, "\x1b[1;6C") + t.prepareKeyMod(KeyCtrlPgUp, ModCtrl, "\x1b[5;5~") + t.prepareKeyMod(KeyCtrlPgDn, ModCtrl, "\x1b[6;5~") + } + +outer: + // Add key mappings for control keys. + for i := 0; i < ' '; i++ { + // Do not insert direct key codes for ambiguous keys. + // For example, ESC is used for lots of other keys, so + // when parsing this we don't want to fast path handling + // of it, but instead wait a bit before parsing it as in + // isolation. + for esc := range t.keycodes { + if []byte(esc)[0] == byte(i) { + continue outer + } + } + + t.keyexist[Key(i)] = true + + mod := ModCtrl + switch Key(i) { + case KeyBS, KeyTAB, KeyESC, KeyCR: + // directly typeable- no control sequence + mod = ModNone + } + t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod} + } +} + +func (t *tScreen) ResetTitle() { + if t.hasSetTitle { + //Reset terminal title. USERNAME for Windows support. Assumes USER and USERNAME will not both be set. + wd, _ := os.Getwd() + host, _ := os.Hostname() + var titlestring string + if strings.Contains(os.Getenv("TERM"), "xterm") { + titlestring = "\033]2;" + os.Getenv("USER") + os.Getenv("USERNAME") + "@" + host + ": " + wd + "\007" + t.TPuts(titlestring) + } + if os.Getenv("TERM") == "screen" { + for _, s := range strings.Split(os.Getenv("SHELL"), "/") { + titlestring = "\033k" + s + "\033\\" + titlestring = "\033]2;" + os.Getenv("USER") + os.Getenv("USERNAME") + "@" + host + ": " + wd + "\007" + } + t.TPuts(titlestring) + } + } +} + +func (t *tScreen) Fini() { + t.Lock() + defer t.Unlock() + + ti := t.ti + t.cells.Resize(0, 0) + t.TPuts(ti.ShowCursor) + t.TPuts(ti.AttrOff) + t.ResetTitle() + t.TPuts(ti.Clear) + t.TPuts(ti.ExitCA) + t.TPuts(ti.ExitKeypad) + // Close bracketed paste + t.TPuts("\x1b[?2004l") + t.DisableMouse() + t.curstyle = Style(-1) + t.clear = false + t.fini = true + + select { + case <-t.quit: + // do nothing, already closed + + default: + close(t.quit) + } + + t.termioFini() +} + +func (t *tScreen) SetStyle(style Style) { + t.Lock() + if !t.fini { + t.style = style + } + t.Unlock() +} + +func (t *tScreen) Clear() { + t.Fill(' ', t.style) +} + +func (t *tScreen) Fill(r rune, style Style) { + t.Lock() + if !t.fini { + t.cells.Fill(r, style) + } + t.Unlock() +} + +func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) { + t.Lock() + if !t.fini { + t.cells.SetContent(x, y, mainc, combc, style) + } + t.Unlock() +} + +func (t *tScreen) GetContent(x, y int) (rune, []rune, Style, int) { + t.Lock() + mainc, combc, style, width := t.cells.GetContent(x, y) + t.Unlock() + return mainc, combc, style, width +} + +func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) { + if len(ch) > 0 { + t.SetContent(x, y, ch[0], ch[1:], style) + } else { + t.SetContent(x, y, ' ', nil, style) + } +} + +func (t *tScreen) encodeRune(r rune, buf []byte) []byte { + + nb := make([]byte, 6) + ob := make([]byte, 6) + num := utf8.EncodeRune(ob, r) + ob = ob[:num] + dst := 0 + var err error + if enc := t.encoder; enc != nil { + enc.Reset() + dst, _, err = enc.Transform(nb, ob, true) + } + if err != nil || dst == 0 || nb[0] == '\x1a' { + // Combining characters are elided + if len(buf) == 0 { + if acs, ok := t.acs[r]; ok { + buf = append(buf, []byte(acs)...) + } else if fb, ok := t.fallback[r]; ok { + buf = append(buf, []byte(fb)...) + } else { + buf = append(buf, '?') + } + } + } else { + buf = append(buf, nb[:dst]...) + } + + return buf +} + +func (t *tScreen) sendFgBg(fg Color, bg Color) { + ti := t.ti + if ti.Colors == 0 { + return + } + if t.truecolor { + if ti.SetFgBgRGB != "" && + fg != ColorDefault && bg != ColorDefault { + r1, g1, b1 := fg.RGB() + r2, g2, b2 := bg.RGB() + t.TPuts(ti.TParm(ti.SetFgBgRGB, + int(r1), int(g1), int(b1), + int(r2), int(g2), int(b2))) + } else { + if fg != ColorDefault && ti.SetFgRGB != "" { + r, g, b := fg.RGB() + t.TPuts(ti.TParm(ti.SetFgRGB, + int(r), int(g), int(b))) + } + if bg != ColorDefault && ti.SetBgRGB != "" { + r, g, b := bg.RGB() + t.TPuts(ti.TParm(ti.SetBgRGB, + int(r), int(g), int(b))) + } + } + return + } + + if fg != ColorDefault { + if v, ok := t.colors[fg]; ok { + fg = v + } else { + v = FindColor(fg, t.palette) + t.colors[fg] = v + fg = v + } + } + + if bg != ColorDefault { + if v, ok := t.colors[bg]; ok { + bg = v + } else { + v = FindColor(bg, t.palette) + t.colors[bg] = v + bg = v + } + } + + if ti.SetFgBg != "" && fg != ColorDefault && bg != ColorDefault { + t.TPuts(ti.TParm(ti.SetFgBg, int(fg), int(bg))) + } else { + if fg != ColorDefault && ti.SetFg != "" { + t.TPuts(ti.TParm(ti.SetFg, int(fg))) + } + if bg != ColorDefault && ti.SetBg != "" { + t.TPuts(ti.TParm(ti.SetBg, int(bg))) + } + } +} + +func (t *tScreen) drawCell(x, y int) int { + + ti := t.ti + + mainc, combc, style, width := t.cells.GetContent(x, y) + if !t.cells.Dirty(x, y) { + return width + } + + if t.cy != y || t.cx != x { + t.TPuts(ti.TGoto(x, y)) + t.cx = x + t.cy = y + } + + if style == StyleDefault { + style = t.style + } + if style != t.curstyle { + fg, bg, attrs := style.Decompose() + + t.TPuts(ti.AttrOff) + + t.sendFgBg(fg, bg) + if attrs&AttrBold != 0 { + t.TPuts(ti.Bold) + } + if attrs&AttrItalic != 0 { + t.TPuts(ti.Italic) + } + if attrs&AttrStrikethrough != 0 { + t.TPuts(ti.Strikethrough) + } + if attrs&AttrUnderline != 0 { + t.TPuts(ti.Underline) + } + if attrs&AttrReverse != 0 { + t.TPuts(ti.Reverse) + } + if attrs&AttrBlink != 0 { + t.TPuts(ti.Blink) + } + if attrs&AttrDim != 0 { + t.TPuts(ti.Dim) + } + t.curstyle = style + } + // now emit runes - taking care to not overrun width with a + // wide character, and to ensure that we emit exactly one regular + // character followed up by any residual combing characters + + if width < 1 { + width = 1 + } + + var str string + + buf := make([]byte, 0, 6) + + buf = t.encodeRune(mainc, buf) + for _, r := range combc { + buf = t.encodeRune(r, buf) + } + + str = string(buf) + if width > 1 && str == "?" { + // No FullWidth character support + str = "? " + t.cx = -1 + } + + // XXX: check for hazeltine not being able to display ~ + + if x > t.w-width { + // too wide to fit; emit a single space instead + width = 1 + str = " " + } + io.WriteString(t.out, str) + t.cx += width + t.cells.SetDirty(x, y, false) + if width > 1 { + t.cx = -1 + } + + return width +} + +func (t *tScreen) ShowCursor(x, y int) { + t.Lock() + t.cursorx = x + t.cursory = y + t.Unlock() +} + +func (t *tScreen) HideCursor() { + t.ShowCursor(-1, -1) +} + +func (t *tScreen) showCursor() { + + x, y := t.cursorx, t.cursory + w, h := t.cells.Size() + if x < 0 || y < 0 || x >= w || y >= h { + t.hideCursor() + return + } + t.TPuts(t.ti.TGoto(x, y)) + t.TPuts(t.ti.ShowCursor) + t.cx = x + t.cy = y +} + +func (t *tScreen) TPuts(s string) { + t.ti.TPuts(t.out, s, t.baud) +} + +func (t *tScreen) Show() { + t.Lock() + if !t.fini { + t.resize() + t.draw() + } + t.Unlock() +} + +func (t *tScreen) clearScreen() { + fg, bg, _ := t.style.Decompose() + t.sendFgBg(fg, bg) + t.TPuts(t.ti.Clear) + t.clear = false +} + +func (t *tScreen) hideCursor() { + // does not update cursor position + if t.ti.HideCursor != "" { + t.TPuts(t.ti.HideCursor) + } else { + // No way to hide cursor, stick it + // at bottom right of screen + t.cx, t.cy = t.cells.Size() + t.TPuts(t.ti.TGoto(t.cx, t.cy)) + } +} + +func (t *tScreen) draw() { + // Buffer all output instead of sending it directly to the terminal + // We'll send it when the draw is over + buf := &bytes.Buffer{} + out := t.out + t.out = buf + + // clobber cursor position, because we're gonna change it all + t.cx = -1 + t.cy = -1 + + // hide the cursor while we move stuff around + t.hideCursor() + + if t.clear { + t.clearScreen() + } + + for y := 0; y < t.h; y++ { + for x := 0; x < t.w; x++ { + width := t.drawCell(x, y) + if width > 1 { + if x+1 < t.w { + // this is necessary so that if we ever + // go back to drawing that cell, we + // actually will *draw* it. + t.cells.SetDirty(x+1, y, true) + } + } + x += width - 1 + } + } + + // Send everything to the terminal + t.out = out + io.WriteString(t.out, buf.String()) + + // restore the cursor + t.showCursor() +} + +func (t *tScreen) EnableMouse() { + if len(t.mouse) != 0 { + t.TPuts(t.ti.TParm(t.ti.MouseMode, 1)) + } +} + +func (t *tScreen) DisableMouse() { + if len(t.mouse) != 0 { + if t.ti.MouseMode == "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" { + t.TPuts("\x1b[?1006l\x1b[?1015l\x1b[?10021l\x1b[?1000l") + } else { + t.TPuts(t.ti.TParm(t.ti.MouseMode, 0)) + } + } +} + +func (t *tScreen) Size() (int, int) { + t.Lock() + w, h := t.w, t.h + t.Unlock() + return w, h +} + +func (t *tScreen) resize() { + if w, h, e := t.getWinSize(); e == nil { + if w != t.w || h != t.h { + t.cx = -1 + t.cy = -1 + + t.cells.Resize(w, h) + t.cells.Invalidate() + t.h = h + t.w = w + ev := NewEventResize(w, h) + t.PostEvent(ev) + } + } +} + +func (t *tScreen) Colors() int { + // this doesn't change, no need for lock + if t.truecolor { + return 1 << 24 + } + return t.ti.Colors +} + +func (t *tScreen) PollEvent() Event { + select { + case <-t.quit: + return nil + case ev := <-t.evch: + return ev + } +} + +// vtACSNames is a map of bytes defined by terminfo that are used in +// the terminals Alternate Character Set to represent other glyphs. +// For example, the upper left corner of the box drawing set can be +// displayed by printing "l" while in the alternate character set. +// Its not quite that simple, since the "l" is the terminfo name, +// and it may be necessary to use a different character based on +// the terminal implementation (or the terminal may lack support for +// this altogether). See buildAcsMap below for detail. +var vtACSNames = map[byte]rune{ + '+': RuneRArrow, + ',': RuneLArrow, + '-': RuneUArrow, + '.': RuneDArrow, + '0': RuneBlock, + '`': RuneDiamond, + 'a': RuneCkBoard, + 'b': '␉', // VT100, Not defined by terminfo + 'c': '␌', // VT100, Not defined by terminfo + 'd': '␋', // VT100, Not defined by terminfo + 'e': '␊', // VT100, Not defined by terminfo + 'f': RuneDegree, + 'g': RunePlMinus, + 'h': RuneBoard, + 'i': RuneLantern, + 'j': RuneLRCorner, + 'k': RuneURCorner, + 'l': RuneULCorner, + 'm': RuneLLCorner, + 'n': RunePlus, + 'o': RuneS1, + 'p': RuneS3, + 'q': RuneHLine, + 'r': RuneS7, + 's': RuneS9, + 't': RuneLTee, + 'u': RuneRTee, + 'v': RuneBTee, + 'w': RuneTTee, + 'x': RuneVLine, + 'y': RuneLEqual, + 'z': RuneGEqual, + '{': RunePi, + '|': RuneNEqual, + '}': RuneSterling, + '~': RuneBullet, +} + +// buildAcsMap builds a map of characters that we translate from Unicode to +// alternate character encodings. To do this, we use the standard VT100 ACS +// maps. This is only done if the terminal lacks support for Unicode; we +// always prefer to emit Unicode glyphs when we are able. +func (t *tScreen) buildAcsMap() { + acsstr := t.ti.AltChars + t.acs = make(map[rune]string) + for len(acsstr) > 2 { + srcv := acsstr[0] + dstv := string(acsstr[1]) + if r, ok := vtACSNames[srcv]; ok { + t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs + } + acsstr = acsstr[2:] + } +} + +func (t *tScreen) PostEventWait(ev Event) { + t.evch <- ev +} + +func (t *tScreen) PostEvent(ev Event) error { + select { + case t.evch <- ev: + return nil + default: + return ErrEventQFull + } +} + +func (t *tScreen) clip(x, y int) (int, int) { + w, h := t.cells.Size() + if x < 0 { + x = 0 + } + if y < 0 { + y = 0 + } + if x > w-1 { + x = w - 1 + } + if y > h-1 { + y = h - 1 + } + return x, y +} + +func (t *tScreen) postMouseEvent(x, y, btn int, motion bool) { + + // XTerm mouse events only report at most one button at a time, + // which may include a wheel button. Wheel motion events are + // reported as single impulses, while other button events are reported + // as separate press & release events. + + button := ButtonNone + mod := ModNone + + // Mouse wheel has bit 6 set, no release events. It should be noted + // that wheel events are sometimes misdelivered as mouse button events + // during a click-drag, so we debounce these, considering them to be + // button press events unless we see an intervening release event. + switch btn & 0x43 { + case 0: + button = Button1 + t.wasbtn = true + case 1: + button = Button2 + t.wasbtn = true + case 2: + button = Button3 + t.wasbtn = true + case 3: + button = ButtonNone + t.wasbtn = false + case 0x40: + if !t.wasbtn { + button = WheelUp + } else { + button = Button1 + } + case 0x41: + if !t.wasbtn { + button = WheelDown + } else { + button = Button2 + } + } + + if btn&0x4 != 0 { + mod |= ModShift + } + if btn&0x8 != 0 { + mod |= ModAlt + } + if btn&0x10 != 0 { + mod |= ModCtrl + } + + // Some terminals will report mouse coordinates outside the + // screen, especially with click-drag events. Clip the coordinates + // to the screen in that case. + x, y = t.clip(x, y) + + ev := NewEventMouse(x, y, button, mod, motion) + t.PostEvent(ev) +} + +// parseSgrMouse attempts to locate an SGR mouse record at the start of the +// buffer. It returns true, true if it found one, and the associated bytes +// be removed from the buffer. It returns true, false if the buffer might +// contain such an event, but more bytes are necessary (partial match), and +// false, false if the content is definitely *not* an SGR mouse record. +func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) { + + b := buf.Bytes() + + var x, y, btn, state int + dig := false + neg := false + motion := false + i := 0 + val := 0 + + for i = range b { + switch b[i] { + case '\x1b': + if state != 0 { + return false, false + } + state = 1 + + case '\x9b': + if state != 0 { + return false, false + } + state = 2 + + case '[': + if state != 1 { + return false, false + } + state = 2 + + case '<': + if state != 2 { + return false, false + } + val = 0 + dig = false + neg = false + state = 3 + + case '-': + if state != 3 && state != 4 && state != 5 { + return false, false + } + if dig || neg { + return false, false + } + neg = true // stay in state + + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + if state != 3 && state != 4 && state != 5 { + return false, false + } + val *= 10 + val += int(b[i] - '0') + dig = true // stay in state + + case ';': + if neg { + val = -val + } + switch state { + case 3: + btn, val = val, 0 + neg, dig, state = false, false, 4 + case 4: + x, val = val-1, 0 + neg, dig, state = false, false, 5 + default: + return false, false + } + + case 'm', 'M': + if state != 5 { + return false, false + } + if neg { + val = -val + } + y = val - 1 + + motion = (btn & 32) != 0 + btn &^= 32 + if b[i] == 'm' { + // mouse release, clear all buttons + btn |= 3 + btn &^= 0x40 + t.buttondn = false + } else if motion { + /* + * Some broken terminals appear to send + * mouse button one motion events, instead of + * encoding 35 (no buttons) into these events. + * We resolve these by looking for a non-motion + * event first. + */ + if !t.buttondn { + btn |= 3 + btn &^= 0x40 + } + } else { + t.buttondn = true + } + // consume the event bytes + for i >= 0 { + buf.ReadByte() + i-- + } + t.postMouseEvent(x, y, btn, motion) + return true, true + } + } + + // incomplete & inconclusve at this point + return true, false +} + +// parseXtermMouse is like parseSgrMouse, but it parses a legacy +// X11 mouse record. +func (t *tScreen) parseXtermMouse(buf *bytes.Buffer) (bool, bool) { + + b := buf.Bytes() + + state := 0 + btn := 0 + x := 0 + y := 0 + + for i := range b { + switch state { + case 0: + switch b[i] { + case '\x1b': + state = 1 + case '\x9b': + state = 2 + default: + return false, false + } + case 1: + if b[i] != '[' { + return false, false + } + state = 2 + case 2: + if b[i] != 'M' { + return false, false + } + state++ + case 3: + btn = int(b[i]) + if btn != 128 && btn != 129 { + btn = int(b[i]) + } else { + btn = 99 + } + state++ + case 4: + x = int(b[i]) - 32 - 1 + state++ + case 5: + y = int(b[i]) - 32 - 1 + for i >= 0 { + buf.ReadByte() + i-- + } + t.postMouseEvent(x, y, btn, false) + return true, true + } + } + return true, false +} + +func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (bool, bool) { + b := buf.Bytes() + partial := false + for e, k := range t.keycodes { + esc := []byte(e) + if (len(esc) == 1) && (esc[0] == '\x1b') { + continue + } + if bytes.HasPrefix(b, esc) { + // matched + var r rune + if len(esc) == 1 { + r = rune(b[0]) + } + mod := k.mod + if t.escaped { + mod |= ModAlt + t.escaped = false + } + ev := NewEventKey(k.key, r, mod) + t.PostEvent(ev) + for i := 0; i < len(esc); i++ { + buf.ReadByte() + } + return true, true + } + if bytes.HasPrefix(esc, b) { + partial = true + } + } + return partial, false +} + +func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) { + b := buf.Bytes() + if b[0] >= ' ' && b[0] <= 0x7F { + // printable ASCII easy to deal with -- no encodings + mod := ModNone + if t.escaped { + mod = ModAlt + t.escaped = false + } + ev := NewEventKey(KeyRune, rune(b[0]), mod) + t.PostEvent(ev) + buf.ReadByte() + return true, true + } + + if b[0] < 0x80 { + // Low numbered values are control keys, not runes. + return false, false + } + + utfb := make([]byte, 12) + for l := 1; l <= len(b); l++ { + t.decoder.Reset() + nout, nin, e := t.decoder.Transform(utfb, b[:l], true) + if e == transform.ErrShortSrc { + continue + } + if nout != 0 { + r, _ := utf8.DecodeRune(utfb[:nout]) + if r != utf8.RuneError { + mod := ModNone + if t.escaped { + mod = ModAlt + t.escaped = false + } + ev := NewEventKey(KeyRune, r, mod) + t.PostEvent(ev) + } + for nin > 0 { + buf.ReadByte() + nin-- + } + return true, true + } + } + // Looks like potential escape + return true, false +} + +func (t *tScreen) parseBracketedPaste(buf *bytes.Buffer) (bool, bool) { + b := buf.Bytes() + + // Replace all carriage returns with newlines + str := strings.Replace(string(b), "\r", "\n", -1) + if strings.HasPrefix(str, "\x1b[200~") { + // The bracketed paste has started + if strings.HasSuffix(str, "\x1b[201~") { + // The bracketed paste has ended + // Strip out the start and end sequences + ev := NewEventPaste(str[6 : len(b)-6]) + t.PostEvent(ev) + for i := 0; i < len(b); i++ { + buf.ReadByte() + } + return true, true + } + // There is still more coming + return true, false + } + return false, false +} + +func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) { + + t.Lock() + defer t.Unlock() + + for { + b := buf.Bytes() + if len(b) == 0 { + buf.Reset() + return + } + if !bytes.Contains(b, []byte("\x1b")) && utf8.RuneCount(b) > 1 { + ev := &EventPaste{t: time.Now(), text: string(bytes.Replace(b, []byte("\r"), []byte("\n"), -1))} + t.PostEvent(ev) + for i := 0; i < len(b); i++ { + buf.ReadByte() + } + continue + } + + partials := 0 + + if part, comp := t.parseBracketedPaste(buf); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseRune(buf); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseFunctionKey(buf); comp { + continue + } else if part { + partials++ + } + + // Only parse mouse records if this term claims to have + // mouse support + + if t.ti.Mouse != "" { + if part, comp := t.parseXtermMouse(buf); comp { + continue + } else if part { + partials++ + } + + if part, comp := t.parseSgrMouse(buf); comp { + continue + } else if part { + partials++ + } + } + + if partials == 0 || expire { + // Nothing was going to match, or we timed out + // waiting for more data -- just deliver the characters + // to the app & let them sort it out. Possibly we + // should only do this for control characters like ESC. + if b[0] == '\x1b' { + if len(b) == 1 { + ev := NewEventKey(KeyEsc, 0, ModNone) + t.PostEvent(ev) + t.escaped = false + } else { + t.escaped = true + } + buf.ReadByte() + continue + } + + by, _ := buf.ReadByte() + mod := ModNone + if t.escaped { + t.escaped = false + mod = ModAlt + } + ev := NewEventKey(KeyRune, rune(by), mod) + t.PostEvent(ev) + continue + } + + // well we have some partial data, wait until we get + // some more + break + } +} + +func (t *tScreen) mainLoop() { + buf := &bytes.Buffer{} + for { + select { + case <-t.quit: + close(t.indoneq) + return + case <-t.sigwinch: + t.Lock() + t.cx = -1 + t.cy = -1 + t.resize() + t.cells.Invalidate() + t.draw() + t.Unlock() + continue + case <-t.keytimer.C: + // If the timer fired, and the current time + // is after the expiration of the escape sequence, + // then we assume the escape sequence reached it's + // conclusion, and process the chunk independently. + // This lets us detect conflicts such as a lone ESC. + if buf.Len() > 0 { + if time.Now().After(t.keyexpire) { + t.scanInput(buf, true) + } + } + if buf.Len() > 0 { + if !t.keytimer.Stop() { + select { + case <-t.keytimer.C: + default: + } + } + t.keytimer.Reset(time.Millisecond * 50) + } + case chunk := <-t.keychan: + buf.Write(chunk) + t.keyexpire = time.Now().Add(time.Millisecond * 50) + t.scanInput(buf, false) + if !t.keytimer.Stop() { + select { + case <-t.keytimer.C: + default: + } + } + if buf.Len() > 0 { + t.keytimer.Reset(time.Millisecond * 50) + } + } + } +} + +func (t *tScreen) inputLoop() { + + for { + chunk := make([]byte, 128) + n, e := t.in.Read(chunk) + switch e { + case io.EOF: + case nil: + default: + t.PostEvent(NewEventError(e)) + return + } + t.keychan <- chunk[:n] + } +} + +func (t *tScreen) Sync() { + t.Lock() + t.cx = -1 + t.cy = -1 + if !t.fini { + t.resize() + t.clear = true + t.cells.Invalidate() + t.draw() + } + t.Unlock() +} + +func (t *tScreen) CharacterSet() string { + return t.charset +} + +func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) { + t.Lock() + t.fallback[orig] = fallback + t.Unlock() +} + +func (t *tScreen) UnregisterRuneFallback(orig rune) { + t.Lock() + delete(t.fallback, orig) + t.Unlock() +} + +func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { + + if enc := t.encoder; enc != nil { + nb := make([]byte, 6) + ob := make([]byte, 6) + num := utf8.EncodeRune(ob, r) + + enc.Reset() + dst, _, err := enc.Transform(nb, ob[:num], true) + if dst != 0 && err == nil && nb[0] != '\x1A' { + return true + } + } + // Terminal fallbacks always permitted, since we assume they are + // basically nearly perfect renditions. + if _, ok := t.acs[r]; ok { + return true + } + if !checkFallbacks { + return false + } + if _, ok := t.fallback[r]; ok { + return true + } + return false +} + +func (t *tScreen) HasMouse() bool { + return len(t.mouse) != 0 +} + +func (t *tScreen) HasKey(k Key) bool { + if k == KeyRune { + return true + } + return t.keyexist[k] +} + +func (t *tScreen) Resize(int, int, int, int) {} + +func (t *tScreen) SetTitle(title string) { + if strings.Compare(os.Getenv("TERM"), "screen") == 0 { + t.hasSetTitle = true + t.TPuts("\033k" + title + "\033\\") + } + if strings.Contains(os.Getenv("TERM"), "xterm") { + t.TPuts("\033]2;" + title + "\007") + t.hasSetTitle = true + } +} -- cgit v1.2.3