289b30715d
This commit converts the dependency management from Godeps to the vendor folder, also switching the tool from godep to trash. Since the upstream tool lacks a few features proposed via a few PRs, until those PRs are merged in (if), use github.com/karalabe/trash. You can update dependencies via trash --update. All dependencies have been updated to their latest version. Parts of the build system are reworked to drop old notions of Godeps and invocation of the go vet command so that it doesn't run against the vendor folder, as that will just blow up during vetting. The conversion drops OpenCL (and hence GPU mining support) from ethash and our codebase. The short reasoning is that there's noone to maintain and having opencl libs in our deps messes up builds as go install ./... tries to build them, failing with unsatisfied link errors for the C OpenCL deps. golang.org/x/net/context is not vendored in. We expect it to be fetched by the user (i.e. using go get). To keep ci.go builds reproducible the package is "vendored" in build/_vendor.
368 lines
7.9 KiB
Go
368 lines
7.9 KiB
Go
// +build linux darwin openbsd freebsd netbsd
|
|
|
|
package liner
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
type nexter struct {
|
|
r rune
|
|
err error
|
|
}
|
|
|
|
// State represents an open terminal
|
|
type State struct {
|
|
commonState
|
|
origMode termios
|
|
defaultMode termios
|
|
next <-chan nexter
|
|
winch chan os.Signal
|
|
pending []rune
|
|
useCHA bool
|
|
}
|
|
|
|
// NewLiner initializes a new *State, and sets the terminal into raw mode. To
|
|
// restore the terminal to its previous state, call State.Close().
|
|
//
|
|
// Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will
|
|
// leak a channel every time you call it. Therefore, it is recommened that you
|
|
// upgrade to a newer release of Go, or ensure that NewLiner is only called
|
|
// once.
|
|
func NewLiner() *State {
|
|
var s State
|
|
s.r = bufio.NewReader(os.Stdin)
|
|
|
|
s.terminalSupported = TerminalSupported()
|
|
if m, err := TerminalMode(); err == nil {
|
|
s.origMode = *m.(*termios)
|
|
} else {
|
|
s.inputRedirected = true
|
|
}
|
|
if _, err := getMode(syscall.Stdout); err != 0 {
|
|
s.outputRedirected = true
|
|
}
|
|
if s.inputRedirected && s.outputRedirected {
|
|
s.terminalSupported = false
|
|
}
|
|
if s.terminalSupported && !s.inputRedirected && !s.outputRedirected {
|
|
mode := s.origMode
|
|
mode.Iflag &^= icrnl | inpck | istrip | ixon
|
|
mode.Cflag |= cs8
|
|
mode.Lflag &^= syscall.ECHO | icanon | iexten
|
|
mode.ApplyMode()
|
|
|
|
winch := make(chan os.Signal, 1)
|
|
signal.Notify(winch, syscall.SIGWINCH)
|
|
s.winch = winch
|
|
|
|
s.checkOutput()
|
|
}
|
|
|
|
if !s.outputRedirected {
|
|
s.outputRedirected = !s.getColumns()
|
|
}
|
|
|
|
return &s
|
|
}
|
|
|
|
var errTimedOut = errors.New("timeout")
|
|
|
|
func (s *State) startPrompt() {
|
|
if s.terminalSupported {
|
|
if m, err := TerminalMode(); err == nil {
|
|
s.defaultMode = *m.(*termios)
|
|
mode := s.defaultMode
|
|
mode.Lflag &^= isig
|
|
mode.ApplyMode()
|
|
}
|
|
}
|
|
s.restartPrompt()
|
|
}
|
|
|
|
func (s *State) restartPrompt() {
|
|
next := make(chan nexter)
|
|
go func() {
|
|
for {
|
|
var n nexter
|
|
n.r, _, n.err = s.r.ReadRune()
|
|
next <- n
|
|
// Shut down nexter loop when an end condition has been reached
|
|
if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD {
|
|
close(next)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
s.next = next
|
|
}
|
|
|
|
func (s *State) stopPrompt() {
|
|
if s.terminalSupported {
|
|
s.defaultMode.ApplyMode()
|
|
}
|
|
}
|
|
|
|
func (s *State) nextPending(timeout <-chan time.Time) (rune, error) {
|
|
select {
|
|
case thing, ok := <-s.next:
|
|
if !ok {
|
|
return 0, errors.New("liner: internal error")
|
|
}
|
|
if thing.err != nil {
|
|
return 0, thing.err
|
|
}
|
|
s.pending = append(s.pending, thing.r)
|
|
return thing.r, nil
|
|
case <-timeout:
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, errTimedOut
|
|
}
|
|
// not reached
|
|
return 0, nil
|
|
}
|
|
|
|
func (s *State) readNext() (interface{}, error) {
|
|
if len(s.pending) > 0 {
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
var r rune
|
|
select {
|
|
case thing, ok := <-s.next:
|
|
if !ok {
|
|
return 0, errors.New("liner: internal error")
|
|
}
|
|
if thing.err != nil {
|
|
return nil, thing.err
|
|
}
|
|
r = thing.r
|
|
case <-s.winch:
|
|
s.getColumns()
|
|
return winch, nil
|
|
}
|
|
if r != esc {
|
|
return r, nil
|
|
}
|
|
s.pending = append(s.pending, r)
|
|
|
|
// Wait at most 50 ms for the rest of the escape sequence
|
|
// If nothing else arrives, it was an actual press of the esc key
|
|
timeout := time.After(50 * time.Millisecond)
|
|
flag, err := s.nextPending(timeout)
|
|
if err != nil {
|
|
if err == errTimedOut {
|
|
return flag, nil
|
|
}
|
|
return unknown, err
|
|
}
|
|
|
|
switch flag {
|
|
case '[':
|
|
code, err := s.nextPending(timeout)
|
|
if err != nil {
|
|
if err == errTimedOut {
|
|
return code, nil
|
|
}
|
|
return unknown, err
|
|
}
|
|
switch code {
|
|
case 'A':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return up, nil
|
|
case 'B':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return down, nil
|
|
case 'C':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return right, nil
|
|
case 'D':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return left, nil
|
|
case 'F':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return end, nil
|
|
case 'H':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return home, nil
|
|
case 'Z':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return shiftTab, nil
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
num := []rune{code}
|
|
for {
|
|
code, err := s.nextPending(timeout)
|
|
if err != nil {
|
|
if err == errTimedOut {
|
|
return code, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
switch code {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
num = append(num, code)
|
|
case ';':
|
|
// Modifier code to follow
|
|
// This only supports Ctrl-left and Ctrl-right for now
|
|
x, _ := strconv.ParseInt(string(num), 10, 32)
|
|
if x != 1 {
|
|
// Can't be left or right
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
num = num[:0]
|
|
for {
|
|
code, err = s.nextPending(timeout)
|
|
if err != nil {
|
|
if err == errTimedOut {
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
switch code {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
num = append(num, code)
|
|
case 'C', 'D':
|
|
// right, left
|
|
mod, _ := strconv.ParseInt(string(num), 10, 32)
|
|
if mod != 5 {
|
|
// Not bare Ctrl
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
s.pending = s.pending[:0] // escape code complete
|
|
if code == 'C' {
|
|
return wordRight, nil
|
|
}
|
|
return wordLeft, nil
|
|
default:
|
|
// Not left or right
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
}
|
|
case '~':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
x, _ := strconv.ParseInt(string(num), 10, 32)
|
|
switch x {
|
|
case 2:
|
|
return insert, nil
|
|
case 3:
|
|
return del, nil
|
|
case 5:
|
|
return pageUp, nil
|
|
case 6:
|
|
return pageDown, nil
|
|
case 7:
|
|
return home, nil
|
|
case 8:
|
|
return end, nil
|
|
case 15:
|
|
return f5, nil
|
|
case 17:
|
|
return f6, nil
|
|
case 18:
|
|
return f7, nil
|
|
case 19:
|
|
return f8, nil
|
|
case 20:
|
|
return f9, nil
|
|
case 21:
|
|
return f10, nil
|
|
case 23:
|
|
return f11, nil
|
|
case 24:
|
|
return f12, nil
|
|
default:
|
|
return unknown, nil
|
|
}
|
|
default:
|
|
// unrecognized escape code
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
case 'O':
|
|
code, err := s.nextPending(timeout)
|
|
if err != nil {
|
|
if err == errTimedOut {
|
|
return code, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
s.pending = s.pending[:0] // escape code complete
|
|
switch code {
|
|
case 'c':
|
|
return wordRight, nil
|
|
case 'd':
|
|
return wordLeft, nil
|
|
case 'H':
|
|
return home, nil
|
|
case 'F':
|
|
return end, nil
|
|
case 'P':
|
|
return f1, nil
|
|
case 'Q':
|
|
return f2, nil
|
|
case 'R':
|
|
return f3, nil
|
|
case 'S':
|
|
return f4, nil
|
|
default:
|
|
return unknown, nil
|
|
}
|
|
case 'b':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return altB, nil
|
|
case 'f':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return altF, nil
|
|
case 'y':
|
|
s.pending = s.pending[:0] // escape code complete
|
|
return altY, nil
|
|
default:
|
|
rv := s.pending[0]
|
|
s.pending = s.pending[1:]
|
|
return rv, nil
|
|
}
|
|
|
|
// not reached
|
|
return r, nil
|
|
}
|
|
|
|
// Close returns the terminal to its previous mode
|
|
func (s *State) Close() error {
|
|
stopSignal(s.winch)
|
|
if !s.inputRedirected {
|
|
s.origMode.ApplyMode()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TerminalSupported returns true if the current terminal supports
|
|
// line editing features, and false if liner will use the 'dumb'
|
|
// fallback for input.
|
|
// Note that TerminalSupported does not check all factors that may
|
|
// cause liner to not fully support the terminal (such as stdin redirection)
|
|
func TerminalSupported() bool {
|
|
bad := map[string]bool{"": true, "dumb": true, "cons25": true}
|
|
return !bad[strings.ToLower(os.Getenv("TERM"))]
|
|
}
|