aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2015-03-06 17:39:31 +0800
committerFelix Lange <fjl@twurst.com>2015-03-06 17:39:31 +0800
commit2393de5d6b535a850e8b5d510aa2ae4f940f3d23 (patch)
tree020fdbfeb9046f101b7e2ccab31bfc3c02caf1aa
parent38f6d60e6e699db24b7a850b5999823b9e36d5bb (diff)
downloaddexon-2393de5d6b535a850e8b5d510aa2ae4f940f3d23.tar.gz
dexon-2393de5d6b535a850e8b5d510aa2ae4f940f3d23.tar.zst
dexon-2393de5d6b535a850e8b5d510aa2ae4f940f3d23.zip
Godeps: add github.com/peterh/liner
-rw-r--r--Godeps/Godeps.json4
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/COPYING21
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/README.md95
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/bsdinput.go39
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/common.go219
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/fallbackinput.go57
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/input.go359
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/input_darwin.go39
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/input_linux.go26
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/input_test.go61
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/input_windows.go313
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/line.go864
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/line_test.go90
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/output.go63
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/output_windows.go54
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/prefix_test.go37
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/race_test.go44
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/signal.go12
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/signal_legacy.go11
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/unixmode.go37
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/width.go47
-rw-r--r--Godeps/_workspace/src/github.com/peterh/liner/width_test.go87
22 files changed, 2579 insertions, 0 deletions
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index bf5b59c95..00c6584a9 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -55,6 +55,10 @@
"Rev": "c288002b52e905973b131089a8a7c761d4a2c36a"
},
{
+ "ImportPath": "github.com/peterh/liner",
+ "Rev": "29f6a646557d83e2b6e9ba05c45fbea9c006dbe8"
+ },
+ {
"ImportPath": "github.com/rakyll/globalconf",
"Rev": "415abc325023f1a00cd2d9fa512e0e71745791a2"
},
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/COPYING b/Godeps/_workspace/src/github.com/peterh/liner/COPYING
new file mode 100644
index 000000000..9e8c9f206
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/COPYING
@@ -0,0 +1,21 @@
+Copyright © 2012 Peter Harris
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/README.md b/Godeps/_workspace/src/github.com/peterh/liner/README.md
new file mode 100644
index 000000000..99027c6e2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/README.md
@@ -0,0 +1,95 @@
+Liner
+=====
+
+Liner is a command line editor with history. It was inspired by linenoise;
+everything Unix-like is a VT100 (or is trying very hard to be). If your
+terminal is not pretending to be a VT100, change it. Liner also support
+Windows.
+
+Liner is released under the X11 license (which is similar to the new BSD
+license).
+
+Line Editing
+------------
+
+The following line editing commands are supported on platforms and terminals
+that Liner supports:
+
+Keystroke | Action
+--------- | ------
+Ctrl-A, Home | Move cursor to beginning of line
+Ctrl-E, End | Move cursor to end of line
+Ctrl-B, Left | Move cursor one character left
+Ctrl-F, Right| Move cursor one character right
+Ctrl-Left | Move cursor to previous word
+Ctrl-Right | Move cursor to next word
+Ctrl-D, Del | (if line is *not* empty) Delete character under cursor
+Ctrl-D | (if line *is* empty) End of File - usually quits application
+Ctrl-C | Reset input (create new empty prompt)
+Ctrl-L | Clear screen (line is unmodified)
+Ctrl-T | Transpose previous character with current character
+Ctrl-H, BackSpace | Delete character before cursor
+Ctrl-W | Delete word leading up to cursor
+Ctrl-K | Delete from cursor to end of line
+Ctrl-U | Delete from start of line to cursor
+Ctrl-P, Up | Previous match from history
+Ctrl-N, Down | Next match from history
+Ctrl-R | Reverse Search history (Ctrl-S forward, Ctrl-G cancel)
+Ctrl-Y | Paste from Yank buffer (Alt-Y to paste next yank instead)
+Tab | Next completion
+Shift-Tab | (after Tab) Previous completion
+
+Getting started
+-----------------
+
+```go
+package main
+
+import (
+ "log"
+ "os"
+ "strings"
+
+ "github.com/peterh/liner"
+)
+
+var (
+ history_fn = "/tmp/.liner_history"
+ names = []string{"john", "james", "mary", "nancy"}
+)
+
+func main() {
+ line := liner.NewLiner()
+ defer line.Close()
+
+ line.SetCompleter(func(line string) (c []string) {
+ for _, n := range names {
+ if strings.HasPrefix(n, strings.ToLower(line)) {
+ c = append(c, n)
+ }
+ }
+ return
+ })
+
+ if f, err := os.Open(history_fn); err == nil {
+ line.ReadHistory(f)
+ f.Close()
+ }
+
+ if name, err := line.Prompt("What is your name? "); err != nil {
+ log.Print("Error reading line: ", err)
+ } else {
+ log.Print("Got: ", name)
+ line.AppendHistory(name)
+ }
+
+ if f, err := os.Create(history_fn); err != nil {
+ log.Print("Error writing history file: ", err)
+ } else {
+ line.WriteHistory(f)
+ f.Close()
+ }
+}
+```
+
+For documentation, see http://godoc.org/github.com/peterh/liner
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/bsdinput.go b/Godeps/_workspace/src/github.com/peterh/liner/bsdinput.go
new file mode 100644
index 000000000..4b552d44d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/bsdinput.go
@@ -0,0 +1,39 @@
+// +build openbsd freebsd netbsd
+
+package liner
+
+import "syscall"
+
+const (
+ getTermios = syscall.TIOCGETA
+ setTermios = syscall.TIOCSETA
+)
+
+const (
+ // Input flags
+ inpck = 0x010
+ istrip = 0x020
+ icrnl = 0x100
+ ixon = 0x200
+
+ // Output flags
+ opost = 0x1
+
+ // Control flags
+ cs8 = 0x300
+
+ // Local flags
+ isig = 0x080
+ icanon = 0x100
+ iexten = 0x400
+)
+
+type termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]byte
+ Ispeed int32
+ Ospeed int32
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/common.go b/Godeps/_workspace/src/github.com/peterh/liner/common.go
new file mode 100644
index 000000000..f8753a195
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/common.go
@@ -0,0 +1,219 @@
+/*
+Package liner implements a simple command line editor, inspired by linenoise
+(https://github.com/antirez/linenoise/). This package supports WIN32 in
+addition to the xterm codes supported by everything else.
+*/
+package liner
+
+import (
+ "bufio"
+ "bytes"
+ "container/ring"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+ "sync"
+ "unicode/utf8"
+)
+
+type commonState struct {
+ terminalSupported bool
+ outputRedirected bool
+ inputRedirected bool
+ history []string
+ historyMutex sync.RWMutex
+ completer WordCompleter
+ columns int
+ killRing *ring.Ring
+ ctrlCAborts bool
+ r *bufio.Reader
+ tabStyle TabStyle
+}
+
+// TabStyle is used to select how tab completions are displayed.
+type TabStyle int
+
+// Two tab styles are currently available:
+//
+// TabCircular cycles through each completion item and displays it directly on
+// the prompt
+//
+// TabPrints prints the list of completion items to the screen after a second
+// tab key is pressed. This behaves similar to GNU readline and BASH (which
+// uses readline)
+const (
+ TabCircular TabStyle = iota
+ TabPrints
+)
+
+// ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C
+// if SetCtrlCAborts(true) has been called on the State
+var ErrPromptAborted = errors.New("prompt aborted")
+
+// ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the
+// platform is normally supported, but stdout has been redirected
+var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
+
+// Max elements to save on the killring
+const KillRingMax = 60
+
+// HistoryLimit is the maximum number of entries saved in the scrollback history.
+const HistoryLimit = 1000
+
+// ReadHistory reads scrollback history from r. Returns the number of lines
+// read, and any read error (except io.EOF).
+func (s *State) ReadHistory(r io.Reader) (num int, err error) {
+ s.historyMutex.Lock()
+ defer s.historyMutex.Unlock()
+
+ in := bufio.NewReader(r)
+ num = 0
+ for {
+ line, part, err := in.ReadLine()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return num, err
+ }
+ if part {
+ return num, fmt.Errorf("line %d is too long", num+1)
+ }
+ if !utf8.Valid(line) {
+ return num, fmt.Errorf("invalid string at line %d", num+1)
+ }
+ num++
+ s.history = append(s.history, string(line))
+ if len(s.history) > HistoryLimit {
+ s.history = s.history[1:]
+ }
+ }
+ return num, nil
+}
+
+// WriteHistory writes scrollback history to w. Returns the number of lines
+// successfully written, and any write error.
+//
+// Unlike the rest of liner's API, WriteHistory is safe to call
+// from another goroutine while Prompt is in progress.
+// This exception is to facilitate the saving of the history buffer
+// during an unexpected exit (for example, due to Ctrl-C being invoked)
+func (s *State) WriteHistory(w io.Writer) (num int, err error) {
+ s.historyMutex.RLock()
+ defer s.historyMutex.RUnlock()
+
+ for _, item := range s.history {
+ _, err := fmt.Fprintln(w, item)
+ if err != nil {
+ return num, err
+ }
+ num++
+ }
+ return num, nil
+}
+
+// AppendHistory appends an entry to the scrollback history. AppendHistory
+// should be called iff Prompt returns a valid command.
+func (s *State) AppendHistory(item string) {
+ s.historyMutex.Lock()
+ defer s.historyMutex.Unlock()
+
+ if len(s.history) > 0 {
+ if item == s.history[len(s.history)-1] {
+ return
+ }
+ }
+ s.history = append(s.history, item)
+ if len(s.history) > HistoryLimit {
+ s.history = s.history[1:]
+ }
+}
+
+// Returns the history lines starting with prefix
+func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
+ for _, h := range s.history {
+ if strings.HasPrefix(h, prefix) {
+ ph = append(ph, h)
+ }
+ }
+ return
+}
+
+// Returns the history lines matching the inteligent search
+func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
+ if pattern == "" {
+ return
+ }
+ for _, h := range s.history {
+ if i := strings.Index(h, pattern); i >= 0 {
+ ph = append(ph, h)
+ pos = append(pos, i)
+ }
+ }
+ return
+}
+
+// Completer takes the currently edited line content at the left of the cursor
+// and returns a list of completion candidates.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
+// to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
+type Completer func(line string) []string
+
+// WordCompleter takes the currently edited line with the cursor position and
+// returns the completion candidates for the partial word to be completed.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
+// to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!".
+type WordCompleter func(line string, pos int) (head string, completions []string, tail string)
+
+// SetCompleter sets the completion function that Liner will call to
+// fetch completion candidates when the user presses tab.
+func (s *State) SetCompleter(f Completer) {
+ if f == nil {
+ s.completer = nil
+ return
+ }
+ s.completer = func(line string, pos int) (string, []string, string) {
+ return "", f(line[:pos]), line[pos:]
+ }
+}
+
+// SetWordCompleter sets the completion function that Liner will call to
+// fetch completion candidates when the user presses tab.
+func (s *State) SetWordCompleter(f WordCompleter) {
+ s.completer = f
+}
+
+// SetTabCompletionStyle sets the behvavior when the Tab key is pressed
+// for auto-completion. TabCircular is the default behavior and cycles
+// through the list of candidates at the prompt. TabPrints will print
+// the available completion candidates to the screen similar to BASH
+// and GNU Readline
+func (s *State) SetTabCompletionStyle(tabStyle TabStyle) {
+ s.tabStyle = tabStyle
+}
+
+// ModeApplier is the interface that wraps a representation of the terminal
+// mode. ApplyMode sets the terminal to this mode.
+type ModeApplier interface {
+ ApplyMode() error
+}
+
+// SetCtrlCAborts sets whether Prompt on a supported terminal will return an
+// ErrPromptAborted when Ctrl-C is pressed. The default is false (will not
+// return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT
+// (and Prompt does not return) regardless of the value passed to SetCtrlCAborts.
+func (s *State) SetCtrlCAborts(aborts bool) {
+ s.ctrlCAborts = aborts
+}
+
+func (s *State) promptUnsupported(p string) (string, error) {
+ if !s.inputRedirected {
+ fmt.Print(p)
+ }
+ linebuf, _, err := s.r.ReadLine()
+ if err != nil {
+ return "", err
+ }
+ return string(bytes.TrimSpace(linebuf)), nil
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/fallbackinput.go b/Godeps/_workspace/src/github.com/peterh/liner/fallbackinput.go
new file mode 100644
index 000000000..d9eb79d9e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/fallbackinput.go
@@ -0,0 +1,57 @@
+// +build !windows,!linux,!darwin,!openbsd,!freebsd,!netbsd
+
+package liner
+
+import (
+ "bufio"
+ "errors"
+ "os"
+)
+
+// State represents an open terminal
+type State struct {
+ commonState
+}
+
+// Prompt displays p, and then waits for user input. Prompt does not support
+// line editing on this operating system.
+func (s *State) Prompt(p string) (string, error) {
+ return s.promptUnsupported(p)
+}
+
+// PasswordPrompt is not supported in this OS.
+func (s *State) PasswordPrompt(p string) (string, error) {
+ return "", errors.New("liner: function not supported in this terminal")
+}
+
+// NewLiner initializes a new *State
+//
+// Note that this operating system uses a fallback mode without line
+// editing. Patches welcome.
+func NewLiner() *State {
+ var s State
+ s.r = bufio.NewReader(os.Stdin)
+ return &s
+}
+
+// Close returns the terminal to its previous mode
+func (s *State) Close() error {
+ return nil
+}
+
+// TerminalSupported returns false because line editing is not
+// supported on this platform.
+func TerminalSupported() bool {
+ return false
+}
+
+type noopMode struct{}
+
+func (n noopMode) ApplyMode() error {
+ return nil
+}
+
+// TerminalMode returns a noop InputModeSetter on this platform.
+func TerminalMode() (ModeApplier, error) {
+ return noopMode{}, nil
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/input.go b/Godeps/_workspace/src/github.com/peterh/liner/input.go
new file mode 100644
index 000000000..cf71d2bce
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/input.go
@@ -0,0 +1,359 @@
+// +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.terminalSupported = false
+ s.inputRedirected = true
+ }
+ if _, err := getMode(syscall.Stdout); err != 0 {
+ s.terminalSupported = false
+ s.outputRedirected = true
+ }
+ if s.terminalSupported {
+ 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.getColumns()
+ s.outputRedirected = s.columns <= 0
+ }
+
+ 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 '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.terminalSupported {
+ 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.
+func TerminalSupported() bool {
+ bad := map[string]bool{"": true, "dumb": true, "cons25": true}
+ return !bad[strings.ToLower(os.Getenv("TERM"))]
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/input_darwin.go b/Godeps/_workspace/src/github.com/peterh/liner/input_darwin.go
new file mode 100644
index 000000000..23c9c5da0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/input_darwin.go
@@ -0,0 +1,39 @@
+// +build darwin
+
+package liner
+
+import "syscall"
+
+const (
+ getTermios = syscall.TIOCGETA
+ setTermios = syscall.TIOCSETA
+)
+
+const (
+ // Input flags
+ inpck = 0x010
+ istrip = 0x020
+ icrnl = 0x100
+ ixon = 0x200
+
+ // Output flags
+ opost = 0x1
+
+ // Control flags
+ cs8 = 0x300
+
+ // Local flags
+ isig = 0x080
+ icanon = 0x100
+ iexten = 0x400
+)
+
+type termios struct {
+ Iflag uintptr
+ Oflag uintptr
+ Cflag uintptr
+ Lflag uintptr
+ Cc [20]byte
+ Ispeed uintptr
+ Ospeed uintptr
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/input_linux.go b/Godeps/_workspace/src/github.com/peterh/liner/input_linux.go
new file mode 100644
index 000000000..6ca87124e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/input_linux.go
@@ -0,0 +1,26 @@
+// +build linux
+
+package liner
+
+import "syscall"
+
+const (
+ getTermios = syscall.TCGETS
+ setTermios = syscall.TCSETS
+)
+
+const (
+ icrnl = syscall.ICRNL
+ inpck = syscall.INPCK
+ istrip = syscall.ISTRIP
+ ixon = syscall.IXON
+ opost = syscall.OPOST
+ cs8 = syscall.CS8
+ isig = syscall.ISIG
+ icanon = syscall.ICANON
+ iexten = syscall.IEXTEN
+)
+
+type termios struct {
+ syscall.Termios
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/input_test.go b/Godeps/_workspace/src/github.com/peterh/liner/input_test.go
new file mode 100644
index 000000000..e515a4894
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/input_test.go
@@ -0,0 +1,61 @@
+// +build !windows
+
+package liner
+
+import (
+ "bufio"
+ "bytes"
+ "testing"
+)
+
+func (s *State) expectRune(t *testing.T, r rune) {
+ item, err := s.readNext()
+ if err != nil {
+ t.Fatalf("Expected rune '%c', got error %s\n", r, err)
+ }
+ if v, ok := item.(rune); !ok {
+ t.Fatalf("Expected rune '%c', got non-rune %v\n", r, v)
+ } else {
+ if v != r {
+ t.Fatalf("Expected rune '%c', got rune '%c'\n", r, v)
+ }
+ }
+}
+
+func (s *State) expectAction(t *testing.T, a action) {
+ item, err := s.readNext()
+ if err != nil {
+ t.Fatalf("Expected Action %d, got error %s\n", a, err)
+ }
+ if v, ok := item.(action); !ok {
+ t.Fatalf("Expected Action %d, got non-Action %v\n", a, v)
+ } else {
+ if v != a {
+ t.Fatalf("Expected Action %d, got Action %d\n", a, v)
+ }
+ }
+}
+
+func TestTypes(t *testing.T) {
+ input := []byte{'A', 27, 'B', 27, 91, 68, 27, '[', '1', ';', '5', 'D', 'e'}
+ var s State
+ s.r = bufio.NewReader(bytes.NewBuffer(input))
+
+ next := make(chan nexter)
+ go func() {
+ for {
+ var n nexter
+ n.r, _, n.err = s.r.ReadRune()
+ next <- n
+ }
+ }()
+ s.next = next
+
+ s.expectRune(t, 'A')
+ s.expectRune(t, 27)
+ s.expectRune(t, 'B')
+ s.expectAction(t, left)
+ s.expectAction(t, wordLeft)
+
+ s.expectRune(t, 'e')
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/input_windows.go b/Godeps/_workspace/src/github.com/peterh/liner/input_windows.go
new file mode 100644
index 000000000..cc98719c1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/input_windows.go
@@ -0,0 +1,313 @@
+package liner
+
+import (
+ "bufio"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+ procGetStdHandle = kernel32.NewProc("GetStdHandle")
+ procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
+ procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+ procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
+ procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
+ procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
+ procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
+)
+
+// These names are from the Win32 api, so they use underscores (contrary to
+// what golint suggests)
+const (
+ std_input_handle = uint32(-10 & 0xFFFFFFFF)
+ std_output_handle = uint32(-11 & 0xFFFFFFFF)
+ std_error_handle = uint32(-12 & 0xFFFFFFFF)
+ invalid_handle_value = ^uintptr(0)
+)
+
+type inputMode uint32
+
+// State represents an open terminal
+type State struct {
+ commonState
+ handle syscall.Handle
+ hOut syscall.Handle
+ origMode inputMode
+ defaultMode inputMode
+ key interface{}
+ repeat uint16
+}
+
+const (
+ enableEchoInput = 0x4
+ enableInsertMode = 0x20
+ enableLineInput = 0x2
+ enableMouseInput = 0x10
+ enableProcessedInput = 0x1
+ enableQuickEditMode = 0x40
+ enableWindowInput = 0x8
+)
+
+// NewLiner initializes a new *State, and sets the terminal into raw mode. To
+// restore the terminal to its previous state, call State.Close().
+func NewLiner() *State {
+ var s State
+ hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle))
+ s.handle = syscall.Handle(hIn)
+ hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle))
+ s.hOut = syscall.Handle(hOut)
+
+ s.terminalSupported = true
+ if m, err := TerminalMode(); err == nil {
+ s.origMode = m.(inputMode)
+ mode := s.origMode
+ mode &^= enableEchoInput
+ mode &^= enableInsertMode
+ mode &^= enableLineInput
+ mode &^= enableMouseInput
+ mode |= enableWindowInput
+ mode.ApplyMode()
+ } else {
+ s.inputRedirected = true
+ s.r = bufio.NewReader(os.Stdin)
+ }
+
+ s.getColumns()
+ s.outputRedirected = s.columns <= 0
+
+ return &s
+}
+
+// These names are from the Win32 api, so they use underscores (contrary to
+// what golint suggests)
+const (
+ focus_event = 0x0010
+ key_event = 0x0001
+ menu_event = 0x0008
+ mouse_event = 0x0002
+ window_buffer_size_event = 0x0004
+)
+
+type input_record struct {
+ eventType uint16
+ pad uint16
+ blob [16]byte
+}
+
+type key_event_record struct {
+ KeyDown int32
+ RepeatCount uint16
+ VirtualKeyCode uint16
+ VirtualScanCode uint16
+ Char int16
+ ControlKeyState uint32
+}
+
+// These names are from the Win32 api, so they use underscores (contrary to
+// what golint suggests)
+const (
+ vk_tab = 0x09
+ vk_prior = 0x21
+ vk_next = 0x22
+ vk_end = 0x23
+ vk_home = 0x24
+ vk_left = 0x25
+ vk_up = 0x26
+ vk_right = 0x27
+ vk_down = 0x28
+ vk_insert = 0x2d
+ vk_delete = 0x2e
+ vk_f1 = 0x70
+ vk_f2 = 0x71
+ vk_f3 = 0x72
+ vk_f4 = 0x73
+ vk_f5 = 0x74
+ vk_f6 = 0x75
+ vk_f7 = 0x76
+ vk_f8 = 0x77
+ vk_f9 = 0x78
+ vk_f10 = 0x79
+ vk_f11 = 0x7a
+ vk_f12 = 0x7b
+ yKey = 0x59
+)
+
+const (
+ shiftPressed = 0x0010
+ leftAltPressed = 0x0002
+ leftCtrlPressed = 0x0008
+ rightAltPressed = 0x0001
+ rightCtrlPressed = 0x0004
+
+ modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
+)
+
+func (s *State) readNext() (interface{}, error) {
+ if s.repeat > 0 {
+ s.repeat--
+ return s.key, nil
+ }
+
+ var input input_record
+ pbuf := uintptr(unsafe.Pointer(&input))
+ var rv uint32
+ prv := uintptr(unsafe.Pointer(&rv))
+
+ for {
+ ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)
+
+ if ok == 0 {
+ return nil, err
+ }
+
+ if input.eventType == window_buffer_size_event {
+ xy := (*coord)(unsafe.Pointer(&input.blob[0]))
+ s.columns = int(xy.x)
+ return winch, nil
+ }
+ if input.eventType != key_event {
+ continue
+ }
+ ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
+ if ke.KeyDown == 0 {
+ continue
+ }
+
+ if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed {
+ s.key = shiftTab
+ } else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
+ ke.ControlKeyState&modKeys == rightAltPressed) {
+ s.key = altY
+ } else if ke.Char > 0 {
+ s.key = rune(ke.Char)
+ } else {
+ switch ke.VirtualKeyCode {
+ case vk_prior:
+ s.key = pageUp
+ case vk_next:
+ s.key = pageDown
+ case vk_end:
+ s.key = end
+ case vk_home:
+ s.key = home
+ case vk_left:
+ s.key = left
+ if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
+ if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
+ s.key = wordLeft
+ }
+ }
+ case vk_right:
+ s.key = right
+ if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
+ if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
+ s.key = wordRight
+ }
+ }
+ case vk_up:
+ s.key = up
+ case vk_down:
+ s.key = down
+ case vk_insert:
+ s.key = insert
+ case vk_delete:
+ s.key = del
+ case vk_f1:
+ s.key = f1
+ case vk_f2:
+ s.key = f2
+ case vk_f3:
+ s.key = f3
+ case vk_f4:
+ s.key = f4
+ case vk_f5:
+ s.key = f5
+ case vk_f6:
+ s.key = f6
+ case vk_f7:
+ s.key = f7
+ case vk_f8:
+ s.key = f8
+ case vk_f9:
+ s.key = f9
+ case vk_f10:
+ s.key = f10
+ case vk_f11:
+ s.key = f11
+ case vk_f12:
+ s.key = f12
+ default:
+ // Eat modifier keys
+ // TODO: return Action(Unknown) if the key isn't a
+ // modifier.
+ continue
+ }
+ }
+
+ if ke.RepeatCount > 1 {
+ s.repeat = ke.RepeatCount - 1
+ }
+ return s.key, nil
+ }
+ return unknown, nil
+}
+
+// Close returns the terminal to its previous mode
+func (s *State) Close() error {
+ s.origMode.ApplyMode()
+ return nil
+}
+
+func (s *State) startPrompt() {
+ if m, err := TerminalMode(); err == nil {
+ s.defaultMode = m.(inputMode)
+ mode := s.defaultMode
+ mode &^= enableProcessedInput
+ mode.ApplyMode()
+ }
+}
+
+func (s *State) restartPrompt() {
+}
+
+func (s *State) stopPrompt() {
+ s.defaultMode.ApplyMode()
+}
+
+// TerminalSupported returns true because line editing is always
+// supported on Windows.
+func TerminalSupported() bool {
+ return true
+}
+
+func (mode inputMode) ApplyMode() error {
+ hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
+ if hIn == invalid_handle_value || hIn == 0 {
+ return err
+ }
+ ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode))
+ if ok != 0 {
+ err = nil
+ }
+ return err
+}
+
+// TerminalMode returns the current terminal input mode as an InputModeSetter.
+//
+// This function is provided for convenience, and should
+// not be necessary for most users of liner.
+func TerminalMode() (ModeApplier, error) {
+ var mode inputMode
+ hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
+ if hIn == invalid_handle_value || hIn == 0 {
+ return nil, err
+ }
+ ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode)))
+ if ok != 0 {
+ err = nil
+ }
+ return mode, err
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/line.go b/Godeps/_workspace/src/github.com/peterh/liner/line.go
new file mode 100644
index 000000000..a70fb59e5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/line.go
@@ -0,0 +1,864 @@
+// +build windows linux darwin openbsd freebsd netbsd
+
+package liner
+
+import (
+ "container/ring"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+type action int
+
+const (
+ left action = iota
+ right
+ up
+ down
+ home
+ end
+ insert
+ del
+ pageUp
+ pageDown
+ f1
+ f2
+ f3
+ f4
+ f5
+ f6
+ f7
+ f8
+ f9
+ f10
+ f11
+ f12
+ altY
+ shiftTab
+ wordLeft
+ wordRight
+ winch
+ unknown
+)
+
+const (
+ ctrlA = 1
+ ctrlB = 2
+ ctrlC = 3
+ ctrlD = 4
+ ctrlE = 5
+ ctrlF = 6
+ ctrlG = 7
+ ctrlH = 8
+ tab = 9
+ lf = 10
+ ctrlK = 11
+ ctrlL = 12
+ cr = 13
+ ctrlN = 14
+ ctrlO = 15
+ ctrlP = 16
+ ctrlQ = 17
+ ctrlR = 18
+ ctrlS = 19
+ ctrlT = 20
+ ctrlU = 21
+ ctrlV = 22
+ ctrlW = 23
+ ctrlX = 24
+ ctrlY = 25
+ ctrlZ = 26
+ esc = 27
+ bs = 127
+)
+
+const (
+ beep = "\a"
+)
+
+type tabDirection int
+
+const (
+ tabForward tabDirection = iota
+ tabReverse
+)
+
+func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
+ s.cursorPos(0)
+ _, err := fmt.Print(string(prompt))
+ if err != nil {
+ return err
+ }
+
+ pLen := countGlyphs(prompt)
+ bLen := countGlyphs(buf)
+ pos = countGlyphs(buf[:pos])
+ if pLen+bLen < s.columns {
+ _, err = fmt.Print(string(buf))
+ s.eraseLine()
+ s.cursorPos(pLen + pos)
+ } else {
+ // Find space available
+ space := s.columns - pLen
+ space-- // space for cursor
+ start := pos - space/2
+ end := start + space
+ if end > bLen {
+ end = bLen
+ start = end - space
+ }
+ if start < 0 {
+ start = 0
+ end = space
+ }
+ pos -= start
+
+ // Leave space for markers
+ if start > 0 {
+ start++
+ }
+ if end < bLen {
+ end--
+ }
+ startRune := len(getPrefixGlyphs(buf, start))
+ line := getPrefixGlyphs(buf[startRune:], end-start)
+
+ // Output
+ if start > 0 {
+ fmt.Print("{")
+ }
+ fmt.Print(string(line))
+ if end < bLen {
+ fmt.Print("}")
+ }
+
+ // Set cursor position
+ s.eraseLine()
+ s.cursorPos(pLen + pos)
+ }
+ return err
+}
+
+func longestCommonPrefix(strs []string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+ longest := strs[0]
+
+ for _, str := range strs[1:] {
+ for !strings.HasPrefix(str, longest) {
+ longest = longest[:len(longest)-1]
+ }
+ }
+ // Remove trailing partial runes
+ longest = strings.TrimRight(longest, "\uFFFD")
+ return longest
+}
+
+func (s *State) circularTabs(items []string) func(tabDirection) (string, error) {
+ item := -1
+ return func(direction tabDirection) (string, error) {
+ if direction == tabForward {
+ if item < len(items)-1 {
+ item++
+ } else {
+ item = 0
+ }
+ } else if direction == tabReverse {
+ if item > 0 {
+ item--
+ } else {
+ item = len(items) - 1
+ }
+ }
+ return items[item], nil
+ }
+}
+
+func (s *State) printedTabs(items []string) func(tabDirection) (string, error) {
+ numTabs := 1
+ prefix := longestCommonPrefix(items)
+ return func(direction tabDirection) (string, error) {
+ if len(items) == 1 {
+ return items[0], nil
+ }
+
+ if numTabs == 2 {
+ if len(items) > 100 {
+ fmt.Printf("\nDisplay all %d possibilities? (y or n) ", len(items))
+ for {
+ next, err := s.readNext()
+ if err != nil {
+ return prefix, err
+ }
+
+ if key, ok := next.(rune); ok {
+ if unicode.ToLower(key) == 'n' {
+ return prefix, nil
+ } else if unicode.ToLower(key) == 'y' {
+ break
+ }
+ }
+ }
+ }
+ fmt.Println("")
+ maxWidth := 0
+ for _, item := range items {
+ if len(item) >= maxWidth {
+ maxWidth = len(item) + 1
+ }
+ }
+
+ numColumns := s.columns / maxWidth
+ numRows := len(items) / numColumns
+ if len(items)%numColumns > 0 {
+ numRows++
+ }
+
+ if len(items) <= numColumns {
+ maxWidth = 0
+ }
+ for i := 0; i < numRows; i++ {
+ for j := 0; j < numColumns*numRows; j += numRows {
+ if i+j < len(items) {
+ if maxWidth > 0 {
+ fmt.Printf("%-*s", maxWidth, items[i+j])
+ } else {
+ fmt.Printf("%v ", items[i+j])
+ }
+ }
+ }
+ fmt.Println("")
+ }
+ } else {
+ numTabs++
+ }
+ return prefix, nil
+ }
+}
+
+func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interface{}, error) {
+ if s.completer == nil {
+ return line, pos, rune(esc), nil
+ }
+ head, list, tail := s.completer(string(line), pos)
+ if len(list) <= 0 {
+ return line, pos, rune(esc), nil
+ }
+ hl := utf8.RuneCountInString(head)
+ if len(list) == 1 {
+ s.refresh(p, []rune(head+list[0]+tail), hl+utf8.RuneCountInString(list[0]))
+ return []rune(head + list[0] + tail), hl + utf8.RuneCountInString(list[0]), rune(esc), nil
+ }
+
+ direction := tabForward
+ tabPrinter := s.circularTabs(list)
+ if s.tabStyle == TabPrints {
+ tabPrinter = s.printedTabs(list)
+ }
+
+ for {
+ pick, err := tabPrinter(direction)
+ if err != nil {
+ return line, pos, rune(esc), err
+ }
+ s.refresh(p, []rune(head+pick+tail), hl+utf8.RuneCountInString(pick))
+
+ next, err := s.readNext()
+ if err != nil {
+ return line, pos, rune(esc), err
+ }
+ if key, ok := next.(rune); ok {
+ if key == tab {
+ direction = tabForward
+ continue
+ }
+ if key == esc {
+ return line, pos, rune(esc), nil
+ }
+ }
+ if a, ok := next.(action); ok && a == shiftTab {
+ direction = tabReverse
+ continue
+ }
+ return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil
+ }
+ // Not reached
+ return line, pos, rune(esc), nil
+}
+
+// reverse intelligent search, implements a bash-like history search.
+func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) {
+ p := "(reverse-i-search)`': "
+ s.refresh([]rune(p), origLine, origPos)
+
+ line := []rune{}
+ pos := 0
+ foundLine := string(origLine)
+ foundPos := origPos
+
+ getLine := func() ([]rune, []rune, int) {
+ search := string(line)
+ prompt := "(reverse-i-search)`%s': "
+ return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos
+ }
+
+ history, positions := s.getHistoryByPattern(string(line))
+ historyPos := len(history) - 1
+
+ for {
+ next, err := s.readNext()
+ if err != nil {
+ return []rune(foundLine), foundPos, rune(esc), err
+ }
+
+ switch v := next.(type) {
+ case rune:
+ switch v {
+ case ctrlR: // Search backwards
+ if historyPos > 0 && historyPos < len(history) {
+ historyPos--
+ foundLine = history[historyPos]
+ foundPos = positions[historyPos]
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlS: // Search forward
+ if historyPos < len(history)-1 && historyPos >= 0 {
+ historyPos++
+ foundLine = history[historyPos]
+ foundPos = positions[historyPos]
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlH, bs: // Backspace
+ if pos <= 0 {
+ fmt.Print(beep)
+ } else {
+ n := len(getSuffixGlyphs(line[:pos], 1))
+ line = append(line[:pos-n], line[pos:]...)
+ pos -= n
+
+ // For each char deleted, display the last matching line of history
+ history, positions := s.getHistoryByPattern(string(line))
+ historyPos = len(history) - 1
+ if len(history) > 0 {
+ foundLine = history[historyPos]
+ foundPos = positions[historyPos]
+ } else {
+ foundLine = ""
+ foundPos = 0
+ }
+ }
+ case ctrlG: // Cancel
+ return origLine, origPos, rune(esc), err
+
+ case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
+ ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
+ fallthrough
+ case 0, ctrlC, esc, 28, 29, 30, 31:
+ return []rune(foundLine), foundPos, next, err
+ default:
+ line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
+ pos++
+
+ // For each keystroke typed, display the last matching line of history
+ history, positions = s.getHistoryByPattern(string(line))
+ historyPos = len(history) - 1
+ if len(history) > 0 {
+ foundLine = history[historyPos]
+ foundPos = positions[historyPos]
+ } else {
+ foundLine = ""
+ foundPos = 0
+ }
+ }
+ case action:
+ return []rune(foundLine), foundPos, next, err
+ }
+ s.refresh(getLine())
+ }
+}
+
+// addToKillRing adds some text to the kill ring. If mode is 0 it adds it to a
+// new node in the end of the kill ring, and move the current pointer to the new
+// node. If mode is 1 or 2 it appends or prepends the text to the current entry
+// of the killRing.
+func (s *State) addToKillRing(text []rune, mode int) {
+ // Don't use the same underlying array as text
+ killLine := make([]rune, len(text))
+ copy(killLine, text)
+
+ // Point killRing to a newNode, procedure depends on the killring state and
+ // append mode.
+ if mode == 0 { // Add new node to killRing
+ if s.killRing == nil { // if killring is empty, create a new one
+ s.killRing = ring.New(1)
+ } else if s.killRing.Len() >= KillRingMax { // if killring is "full"
+ s.killRing = s.killRing.Next()
+ } else { // Normal case
+ s.killRing.Link(ring.New(1))
+ s.killRing = s.killRing.Next()
+ }
+ } else {
+ if s.killRing == nil { // if killring is empty, create a new one
+ s.killRing = ring.New(1)
+ s.killRing.Value = []rune{}
+ }
+ if mode == 1 { // Append to last entry
+ killLine = append(s.killRing.Value.([]rune), killLine...)
+ } else if mode == 2 { // Prepend to last entry
+ killLine = append(killLine, s.killRing.Value.([]rune)...)
+ }
+ }
+
+ // Save text in the current killring node
+ s.killRing.Value = killLine
+}
+
+func (s *State) yank(p []rune, text []rune, pos int) ([]rune, int, interface{}, error) {
+ if s.killRing == nil {
+ return text, pos, rune(esc), nil
+ }
+
+ lineStart := text[:pos]
+ lineEnd := text[pos:]
+ var line []rune
+
+ for {
+ value := s.killRing.Value.([]rune)
+ line = make([]rune, 0)
+ line = append(line, lineStart...)
+ line = append(line, value...)
+ line = append(line, lineEnd...)
+
+ pos = len(lineStart) + len(value)
+ s.refresh(p, line, pos)
+
+ next, err := s.readNext()
+ if err != nil {
+ return line, pos, next, err
+ }
+
+ switch v := next.(type) {
+ case rune:
+ return line, pos, next, nil
+ case action:
+ switch v {
+ case altY:
+ s.killRing = s.killRing.Prev()
+ default:
+ return line, pos, next, nil
+ }
+ }
+ }
+
+ return line, pos, esc, nil
+}
+
+// Prompt displays p, and then waits for user input. Prompt allows line editing
+// if the terminal supports it.
+func (s *State) Prompt(prompt string) (string, error) {
+ if s.inputRedirected {
+ return s.promptUnsupported(prompt)
+ }
+ if s.outputRedirected {
+ return "", ErrNotTerminalOutput
+ }
+ if !s.terminalSupported {
+ return s.promptUnsupported(prompt)
+ }
+
+ s.historyMutex.RLock()
+ defer s.historyMutex.RUnlock()
+
+ s.startPrompt()
+ defer s.stopPrompt()
+ s.getColumns()
+
+ fmt.Print(prompt)
+ p := []rune(prompt)
+ var line []rune
+ pos := 0
+ historyEnd := ""
+ prefixHistory := s.getHistoryByPrefix(string(line))
+ historyPos := len(prefixHistory)
+ historyAction := false // used to mark history related actions
+ killAction := 0 // used to mark kill related actions
+mainLoop:
+ for {
+ next, err := s.readNext()
+ haveNext:
+ if err != nil {
+ return "", err
+ }
+
+ historyAction = false
+ switch v := next.(type) {
+ case rune:
+ switch v {
+ case cr, lf:
+ fmt.Println()
+ break mainLoop
+ case ctrlA: // Start of line
+ pos = 0
+ s.refresh(p, line, pos)
+ case ctrlE: // End of line
+ pos = len(line)
+ s.refresh(p, line, pos)
+ case ctrlB: // left
+ if pos > 0 {
+ pos -= len(getSuffixGlyphs(line[:pos], 1))
+ s.refresh(p, line, pos)
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlF: // right
+ if pos < len(line) {
+ pos += len(getPrefixGlyphs(line[pos:], 1))
+ s.refresh(p, line, pos)
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlD: // del
+ if pos == 0 && len(line) == 0 {
+ // exit
+ return "", io.EOF
+ }
+
+ // ctrlD is a potential EOF, so the rune reader shuts down.
+ // Therefore, if it isn't actually an EOF, we must re-startPrompt.
+ s.restartPrompt()
+
+ if pos >= len(line) {
+ fmt.Print(beep)
+ } else {
+ n := len(getPrefixGlyphs(line[pos:], 1))
+ line = append(line[:pos], line[pos+n:]...)
+ s.refresh(p, line, pos)
+ }
+ case ctrlK: // delete remainder of line
+ if pos >= len(line) {
+ fmt.Print(beep)
+ } else {
+ if killAction > 0 {
+ s.addToKillRing(line[pos:], 1) // Add in apend mode
+ } else {
+ s.addToKillRing(line[pos:], 0) // Add in normal mode
+ }
+
+ killAction = 2 // Mark that there was a kill action
+ line = line[:pos]
+ s.refresh(p, line, pos)
+ }
+ case ctrlP: // up
+ historyAction = true
+ if historyPos > 0 {
+ if historyPos == len(prefixHistory) {
+ historyEnd = string(line)
+ }
+ historyPos--
+ line = []rune(prefixHistory[historyPos])
+ pos = len(line)
+ s.refresh(p, line, pos)
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlN: // down
+ historyAction = true
+ if historyPos < len(prefixHistory) {
+ historyPos++
+ if historyPos == len(prefixHistory) {
+ line = []rune(historyEnd)
+ } else {
+ line = []rune(prefixHistory[historyPos])
+ }
+ pos = len(line)
+ s.refresh(p, line, pos)
+ } else {
+ fmt.Print(beep)
+ }
+ case ctrlT: // transpose prev glyph with glyph under cursor
+ if len(line) < 2 || pos < 1 {
+ fmt.Print(beep)
+ } else {
+ if pos == len(line) {
+ pos -= len(getSuffixGlyphs(line, 1))
+ }
+ prev := getSuffixGlyphs(line[:pos], 1)
+ next := getPrefixGlyphs(line[pos:], 1)
+ scratch := make([]rune, len(prev))
+ copy(scratch, prev)
+ copy(line[pos-len(prev):], next)
+ copy(line[pos-len(prev)+len(next):], scratch)
+ pos += len(next)
+ s.refresh(p, line, pos)
+ }
+ case ctrlL: // clear screen
+ s.eraseScreen()
+ s.refresh(p, line, pos)
+ case ctrlC: // reset
+ fmt.Println("^C")
+ if s.ctrlCAborts {
+ return "", ErrPromptAborted
+ }
+ line = line[:0]
+ pos = 0
+ fmt.Print(prompt)
+ s.restartPrompt()
+ case ctrlH, bs: // Backspace
+ if pos <= 0 {
+ fmt.Print(beep)
+ } else {
+ n := len(getSuffixGlyphs(line[:pos], 1))
+ line = append(line[:pos-n], line[pos:]...)
+ pos -= n
+ s.refresh(p, line, pos)
+ }
+ case ctrlU: // Erase line before cursor
+ if killAction > 0 {
+ s.addToKillRing(line[:pos], 2) // Add in prepend mode
+ } else {
+ s.addToKillRing(line[:pos], 0) // Add in normal mode
+ }
+
+ killAction = 2 // Mark that there was some killing
+ line = line[pos:]
+ pos = 0
+ s.refresh(p, line, pos)
+ case ctrlW: // Erase word
+ if pos == 0 {
+ fmt.Print(beep)
+ break
+ }
+ // Remove whitespace to the left
+ var buf []rune // Store the deleted chars in a buffer
+ for {
+ if pos == 0 || !unicode.IsSpace(line[pos-1]) {
+ break
+ }
+ buf = append(buf, line[pos-1])
+ line = append(line[:pos-1], line[pos:]...)
+ pos--
+ }
+ // Remove non-whitespace to the left
+ for {
+ if pos == 0 || unicode.IsSpace(line[pos-1]) {
+ break
+ }
+ buf = append(buf, line[pos-1])
+ line = append(line[:pos-1], line[pos:]...)
+ pos--
+ }
+ // Invert the buffer and save the result on the killRing
+ var newBuf []rune
+ for i := len(buf) - 1; i >= 0; i-- {
+ newBuf = append(newBuf, buf[i])
+ }
+ if killAction > 0 {
+ s.addToKillRing(newBuf, 2) // Add in prepend mode
+ } else {
+ s.addToKillRing(newBuf, 0) // Add in normal mode
+ }
+ killAction = 2 // Mark that there was some killing
+
+ s.refresh(p, line, pos)
+ case ctrlY: // Paste from Yank buffer
+ line, pos, next, err = s.yank(p, line, pos)
+ goto haveNext
+ case ctrlR: // Reverse Search
+ line, pos, next, err = s.reverseISearch(line, pos)
+ s.refresh(p, line, pos)
+ goto haveNext
+ case tab: // Tab completion
+ line, pos, next, err = s.tabComplete(p, line, pos)
+ goto haveNext
+ // Catch keys that do nothing, but you don't want them to beep
+ case esc:
+ // DO NOTHING
+ // Unused keys
+ case ctrlG, ctrlO, ctrlQ, ctrlS, ctrlV, ctrlX, ctrlZ:
+ fallthrough
+ // Catch unhandled control codes (anything <= 31)
+ case 0, 28, 29, 30, 31:
+ fmt.Print(beep)
+ default:
+ if pos == len(line) && len(p)+len(line) < s.columns-1 {
+ line = append(line, v)
+ fmt.Printf("%c", v)
+ pos++
+ } else {
+ line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
+ pos++
+ s.refresh(p, line, pos)
+ }
+ }
+ case action:
+ switch v {
+ case del:
+ if pos >= len(line) {
+ fmt.Print(beep)
+ } else {
+ n := len(getPrefixGlyphs(line[pos:], 1))
+ line = append(line[:pos], line[pos+n:]...)
+ }
+ case left:
+ if pos > 0 {
+ pos -= len(getSuffixGlyphs(line[:pos], 1))
+ } else {
+ fmt.Print(beep)
+ }
+ case wordLeft:
+ if pos > 0 {
+ for {
+ pos--
+ if pos == 0 || unicode.IsSpace(line[pos-1]) {
+ break
+ }
+ }
+ } else {
+ fmt.Print(beep)
+ }
+ case right:
+ if pos < len(line) {
+ pos += len(getPrefixGlyphs(line[pos:], 1))
+ } else {
+ fmt.Print(beep)
+ }
+ case wordRight:
+ if pos < len(line) {
+ for {
+ pos++
+ if pos == len(line) || unicode.IsSpace(line[pos]) {
+ break
+ }
+ }
+ } else {
+ fmt.Print(beep)
+ }
+ case up:
+ historyAction = true
+ if historyPos > 0 {
+ if historyPos == len(prefixHistory) {
+ historyEnd = string(line)
+ }
+ historyPos--
+ line = []rune(prefixHistory[historyPos])
+ pos = len(line)
+ } else {
+ fmt.Print(beep)
+ }
+ case down:
+ historyAction = true
+ if historyPos < len(prefixHistory) {
+ historyPos++
+ if historyPos == len(prefixHistory) {
+ line = []rune(historyEnd)
+ } else {
+ line = []rune(prefixHistory[historyPos])
+ }
+ pos = len(line)
+ } else {
+ fmt.Print(beep)
+ }
+ case home: // Start of line
+ pos = 0
+ case end: // End of line
+ pos = len(line)
+ }
+ s.refresh(p, line, pos)
+ }
+ if !historyAction {
+ prefixHistory = s.getHistoryByPrefix(string(line))
+ historyPos = len(prefixHistory)
+ }
+ if killAction > 0 {
+ killAction--
+ }
+ }
+ return string(line), nil
+}
+
+// PasswordPrompt displays p, and then waits for user input. The input typed by
+// the user is not displayed in the terminal.
+func (s *State) PasswordPrompt(prompt string) (string, error) {
+ if s.inputRedirected {
+ return s.promptUnsupported(prompt)
+ }
+ if s.outputRedirected {
+ return "", ErrNotTerminalOutput
+ }
+ if !s.terminalSupported {
+ return "", errors.New("liner: function not supported in this terminal")
+ }
+
+ s.startPrompt()
+ defer s.stopPrompt()
+ s.getColumns()
+
+ fmt.Print(prompt)
+ p := []rune(prompt)
+ var line []rune
+ pos := 0
+
+mainLoop:
+ for {
+ next, err := s.readNext()
+ if err != nil {
+ return "", err
+ }
+
+ switch v := next.(type) {
+ case rune:
+ switch v {
+ case cr, lf:
+ fmt.Println()
+ break mainLoop
+ case ctrlD: // del
+ if pos == 0 && len(line) == 0 {
+ // exit
+ return "", io.EOF
+ }
+
+ // ctrlD is a potential EOF, so the rune reader shuts down.
+ // Therefore, if it isn't actually an EOF, we must re-startPrompt.
+ s.restartPrompt()
+ case ctrlL: // clear screen
+ s.eraseScreen()
+ s.refresh(p, []rune{}, 0)
+ case ctrlH, bs: // Backspace
+ if pos <= 0 {
+ fmt.Print(beep)
+ } else {
+ n := len(getSuffixGlyphs(line[:pos], 1))
+ line = append(line[:pos-n], line[pos:]...)
+ pos -= n
+ }
+ case ctrlC:
+ fmt.Println("^C")
+ if s.ctrlCAborts {
+ return "", ErrPromptAborted
+ }
+ line = line[:0]
+ pos = 0
+ fmt.Print(prompt)
+ s.restartPrompt()
+ // Unused keys
+ case esc, tab, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlK, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS,
+ ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
+ fallthrough
+ // Catch unhandled control codes (anything <= 31)
+ case 0, 28, 29, 30, 31:
+ fmt.Print(beep)
+ default:
+ line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
+ pos++
+ }
+ }
+ }
+ return string(line), nil
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/line_test.go b/Godeps/_workspace/src/github.com/peterh/liner/line_test.go
new file mode 100644
index 000000000..727da6ce7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/line_test.go
@@ -0,0 +1,90 @@
+package liner
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func TestAppend(t *testing.T) {
+ var s State
+ s.AppendHistory("foo")
+ s.AppendHistory("bar")
+
+ var out bytes.Buffer
+ num, err := s.WriteHistory(&out)
+ if err != nil {
+ t.Fatal("Unexpected error writing history", err)
+ }
+ if num != 2 {
+ t.Fatalf("Expected 2 history entries, got %d", num)
+ }
+
+ s.AppendHistory("baz")
+ num, err = s.WriteHistory(&out)
+ if err != nil {
+ t.Fatal("Unexpected error writing history", err)
+ }
+ if num != 3 {
+ t.Fatalf("Expected 3 history entries, got %d", num)
+ }
+
+ s.AppendHistory("baz")
+ num, err = s.WriteHistory(&out)
+ if err != nil {
+ t.Fatal("Unexpected error writing history", err)
+ }
+ if num != 3 {
+ t.Fatalf("Expected 3 history entries after duplicate append, got %d", num)
+ }
+
+ s.AppendHistory("baz")
+
+}
+
+func TestHistory(t *testing.T) {
+ input := `foo
+bar
+baz
+quux
+dingle`
+
+ var s State
+ num, err := s.ReadHistory(strings.NewReader(input))
+ if err != nil {
+ t.Fatal("Unexpected error reading history", err)
+ }
+ if num != 5 {
+ t.Fatal("Wrong number of history entries read")
+ }
+
+ var out bytes.Buffer
+ num, err = s.WriteHistory(&out)
+ if err != nil {
+ t.Fatal("Unexpected error writing history", err)
+ }
+ if num != 5 {
+ t.Fatal("Wrong number of history entries written")
+ }
+ if strings.TrimSpace(out.String()) != input {
+ t.Fatal("Round-trip failure")
+ }
+
+ // Test reading with a trailing newline present
+ var s2 State
+ num, err = s2.ReadHistory(&out)
+ if err != nil {
+ t.Fatal("Unexpected error reading history the 2nd time", err)
+ }
+ if num != 5 {
+ t.Fatal("Wrong number of history entries read the 2nd time")
+ }
+
+ num, err = s.ReadHistory(strings.NewReader(input + "\n\xff"))
+ if err == nil {
+ t.Fatal("Unexpected success reading corrupted history", err)
+ }
+ if num != 5 {
+ t.Fatal("Wrong number of history entries read the 3rd time")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/output.go b/Godeps/_workspace/src/github.com/peterh/liner/output.go
new file mode 100644
index 000000000..e91f4ea81
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/output.go
@@ -0,0 +1,63 @@
+// +build linux darwin openbsd freebsd netbsd
+
+package liner
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+func (s *State) cursorPos(x int) {
+ if s.useCHA {
+ // 'G' is "Cursor Character Absolute (CHA)"
+ fmt.Printf("\x1b[%dG", x+1)
+ } else {
+ // 'C' is "Cursor Forward (CUF)"
+ fmt.Print("\r")
+ if x > 0 {
+ fmt.Printf("\x1b[%dC", x)
+ }
+ }
+}
+
+func (s *State) eraseLine() {
+ fmt.Print("\x1b[0K")
+}
+
+func (s *State) eraseScreen() {
+ fmt.Print("\x1b[H\x1b[2J")
+}
+
+type winSize struct {
+ row, col uint16
+ xpixel, ypixel uint16
+}
+
+func (s *State) getColumns() {
+ var ws winSize
+ ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout),
+ syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws)))
+ if ok < 0 {
+ s.columns = 80
+ }
+ s.columns = int(ws.col)
+}
+
+func (s *State) checkOutput() {
+ // xterm is known to support CHA
+ if strings.Contains(strings.ToLower(os.Getenv("TERM")), "xterm") {
+ s.useCHA = true
+ return
+ }
+
+ // The test for functional ANSI CHA is unreliable (eg the Windows
+ // telnet command does not support reading the cursor position with
+ // an ANSI DSR request, despite setting TERM=ansi)
+
+ // Assume CHA isn't supported (which should be safe, although it
+ // does result in occasional visible cursor jitter)
+ s.useCHA = false
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/output_windows.go b/Godeps/_workspace/src/github.com/peterh/liner/output_windows.go
new file mode 100644
index 000000000..27ae55a14
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/output_windows.go
@@ -0,0 +1,54 @@
+package liner
+
+import (
+ "unsafe"
+)
+
+type coord struct {
+ x, y int16
+}
+type smallRect struct {
+ left, top, right, bottom int16
+}
+
+type consoleScreenBufferInfo struct {
+ dwSize coord
+ dwCursorPosition coord
+ wAttributes int16
+ srWindow smallRect
+ dwMaximumWindowSize coord
+}
+
+func (s *State) cursorPos(x int) {
+ var sbi consoleScreenBufferInfo
+ procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
+ procSetConsoleCursorPosition.Call(uintptr(s.hOut),
+ uintptr(int(x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16))
+}
+
+func (s *State) eraseLine() {
+ var sbi consoleScreenBufferInfo
+ procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
+ var numWritten uint32
+ procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '),
+ uintptr(sbi.dwSize.x-sbi.dwCursorPosition.x),
+ uintptr(int(sbi.dwCursorPosition.x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16),
+ uintptr(unsafe.Pointer(&numWritten)))
+}
+
+func (s *State) eraseScreen() {
+ var sbi consoleScreenBufferInfo
+ procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
+ var numWritten uint32
+ procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '),
+ uintptr(sbi.dwSize.x)*uintptr(sbi.dwSize.y),
+ 0,
+ uintptr(unsafe.Pointer(&numWritten)))
+ procSetConsoleCursorPosition.Call(uintptr(s.hOut), 0)
+}
+
+func (s *State) getColumns() {
+ var sbi consoleScreenBufferInfo
+ procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
+ s.columns = int(sbi.dwSize.x)
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/prefix_test.go b/Godeps/_workspace/src/github.com/peterh/liner/prefix_test.go
new file mode 100644
index 000000000..c826d6c3b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/prefix_test.go
@@ -0,0 +1,37 @@
+// +build windows linux darwin openbsd freebsd netbsd
+
+package liner
+
+import "testing"
+
+type testItem struct {
+ list []string
+ prefix string
+}
+
+func TestPrefix(t *testing.T) {
+ list := []testItem{
+ {[]string{"food", "foot"}, "foo"},
+ {[]string{"foo", "foot"}, "foo"},
+ {[]string{"food", "foo"}, "foo"},
+ {[]string{"food", "foe", "foot"}, "fo"},
+ {[]string{"food", "foot", "barbeque"}, ""},
+ {[]string{"cafeteria", "café"}, "caf"},
+ {[]string{"cafe", "café"}, "caf"},
+ {[]string{"cafè", "café"}, "caf"},
+ {[]string{"cafés", "café"}, "café"},
+ {[]string{"áéíóú", "áéíóú"}, "áéíóú"},
+ {[]string{"éclairs", "éclairs"}, "éclairs"},
+ {[]string{"éclairs are the best", "éclairs are great", "éclairs"}, "éclairs"},
+ {[]string{"éclair", "éclairs"}, "éclair"},
+ {[]string{"éclairs", "éclair"}, "éclair"},
+ {[]string{"éclair", "élan"}, "é"},
+ }
+
+ for _, test := range list {
+ lcp := longestCommonPrefix(test.list)
+ if lcp != test.prefix {
+ t.Errorf("%s != %s for %+v", lcp, test.prefix, test.list)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/race_test.go b/Godeps/_workspace/src/github.com/peterh/liner/race_test.go
new file mode 100644
index 000000000..e320849c7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/race_test.go
@@ -0,0 +1,44 @@
+// +build race
+
+package liner
+
+import (
+ "io/ioutil"
+ "os"
+ "sync"
+ "testing"
+)
+
+func TestWriteHistory(t *testing.T) {
+ oldout := os.Stdout
+ defer func() { os.Stdout = oldout }()
+ oldin := os.Stdout
+ defer func() { os.Stdin = oldin }()
+
+ newinr, newinw, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ os.Stdin = newinr
+ newoutr, newoutw, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer newoutr.Close()
+ os.Stdout = newoutw
+
+ var wait sync.WaitGroup
+ wait.Add(1)
+ s := NewLiner()
+ go func() {
+ s.AppendHistory("foo")
+ s.AppendHistory("bar")
+ s.Prompt("")
+ wait.Done()
+ }()
+
+ s.WriteHistory(ioutil.Discard)
+
+ newinw.Close()
+ wait.Wait()
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/signal.go b/Godeps/_workspace/src/github.com/peterh/liner/signal.go
new file mode 100644
index 000000000..0cba79e7f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/signal.go
@@ -0,0 +1,12 @@
+// +build go1.1,!windows
+
+package liner
+
+import (
+ "os"
+ "os/signal"
+)
+
+func stopSignal(c chan<- os.Signal) {
+ signal.Stop(c)
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/signal_legacy.go b/Godeps/_workspace/src/github.com/peterh/liner/signal_legacy.go
new file mode 100644
index 000000000..fa3672daa
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/signal_legacy.go
@@ -0,0 +1,11 @@
+// +build !go1.1,!windows
+
+package liner
+
+import (
+ "os"
+)
+
+func stopSignal(c chan<- os.Signal) {
+ // signal.Stop does not exist before Go 1.1
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/unixmode.go b/Godeps/_workspace/src/github.com/peterh/liner/unixmode.go
new file mode 100644
index 000000000..9838923f5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/unixmode.go
@@ -0,0 +1,37 @@
+// +build linux darwin freebsd openbsd netbsd
+
+package liner
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func (mode *termios) ApplyMode() error {
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), setTermios, uintptr(unsafe.Pointer(mode)))
+
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
+
+// TerminalMode returns the current terminal input mode as an InputModeSetter.
+//
+// This function is provided for convenience, and should
+// not be necessary for most users of liner.
+func TerminalMode() (ModeApplier, error) {
+ mode, errno := getMode(syscall.Stdin)
+
+ if errno != 0 {
+ return nil, errno
+ }
+ return mode, nil
+}
+
+func getMode(handle int) (*termios, syscall.Errno) {
+ var mode termios
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(handle), getTermios, uintptr(unsafe.Pointer(&mode)))
+
+ return &mode, errno
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/width.go b/Godeps/_workspace/src/github.com/peterh/liner/width.go
new file mode 100644
index 000000000..02cfb5e1b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/width.go
@@ -0,0 +1,47 @@
+package liner
+
+import "unicode"
+
+// These character classes are mostly zero width (when combined).
+// A few might not be, depending on the user's font. Fixing this
+// is non-trivial, given that some terminals don't support
+// ANSI DSR/CPR
+var zeroWidth = []*unicode.RangeTable{
+ unicode.Mn,
+ unicode.Me,
+ unicode.Cc,
+ unicode.Cf,
+}
+
+func countGlyphs(s []rune) int {
+ n := 0
+ for _, r := range s {
+ if !unicode.IsOneOf(zeroWidth, r) {
+ n++
+ }
+ }
+ return n
+}
+
+func getPrefixGlyphs(s []rune, num int) []rune {
+ p := 0
+ for n := 0; n < num && p < len(s); p++ {
+ if !unicode.IsOneOf(zeroWidth, s[p]) {
+ n++
+ }
+ }
+ for p < len(s) && unicode.IsOneOf(zeroWidth, s[p]) {
+ p++
+ }
+ return s[:p]
+}
+
+func getSuffixGlyphs(s []rune, num int) []rune {
+ p := len(s)
+ for n := 0; n < num && p > 0; p-- {
+ if !unicode.IsOneOf(zeroWidth, s[p-1]) {
+ n++
+ }
+ }
+ return s[p:]
+}
diff --git a/Godeps/_workspace/src/github.com/peterh/liner/width_test.go b/Godeps/_workspace/src/github.com/peterh/liner/width_test.go
new file mode 100644
index 000000000..134920a4b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/peterh/liner/width_test.go
@@ -0,0 +1,87 @@
+package liner
+
+import (
+ "strconv"
+ "testing"
+)
+
+func accent(in []rune) []rune {
+ var out []rune
+ for _, r := range in {
+ out = append(out, r)
+ out = append(out, '\u0301')
+ }
+ return out
+}
+
+var testString = []rune("query")
+
+func TestCountGlyphs(t *testing.T) {
+ count := countGlyphs(testString)
+ if count != len(testString) {
+ t.Errorf("ASCII count incorrect. %d != %d", count, len(testString))
+ }
+ count = countGlyphs(accent(testString))
+ if count != len(testString) {
+ t.Errorf("Accent count incorrect. %d != %d", count, len(testString))
+ }
+}
+
+func compare(a, b []rune, name string, t *testing.T) {
+ if len(a) != len(b) {
+ t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name)
+ return
+ }
+ for i := range a {
+ if a[i] != b[i] {
+ t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name)
+ return
+ }
+ }
+}
+
+func TestPrefixGlyphs(t *testing.T) {
+ for i := 0; i <= len(testString); i++ {
+ iter := strconv.Itoa(i)
+ out := getPrefixGlyphs(testString, i)
+ compare(out, testString[:i], "ascii prefix "+iter, t)
+ out = getPrefixGlyphs(accent(testString), i)
+ compare(out, accent(testString[:i]), "accent prefix "+iter, t)
+ }
+ out := getPrefixGlyphs(testString, 999)
+ compare(out, testString, "ascii prefix overflow", t)
+ out = getPrefixGlyphs(accent(testString), 999)
+ compare(out, accent(testString), "accent prefix overflow", t)
+
+ out = getPrefixGlyphs(testString, -3)
+ if len(out) != 0 {
+ t.Error("ascii prefix negative")
+ }
+ out = getPrefixGlyphs(accent(testString), -3)
+ if len(out) != 0 {
+ t.Error("accent prefix negative")
+ }
+}
+
+func TestSuffixGlyphs(t *testing.T) {
+ for i := 0; i <= len(testString); i++ {
+ iter := strconv.Itoa(i)
+ out := getSuffixGlyphs(testString, i)
+ compare(out, testString[len(testString)-i:], "ascii suffix "+iter, t)
+ out = getSuffixGlyphs(accent(testString), i)
+ compare(out, accent(testString[len(testString)-i:]), "accent suffix "+iter, t)
+ }
+ out := getSuffixGlyphs(testString, 999)
+ compare(out, testString, "ascii suffix overflow", t)
+ out = getSuffixGlyphs(accent(testString), 999)
+ compare(out, accent(testString), "accent suffix overflow", t)
+
+ out = getSuffixGlyphs(testString, -3)
+ if len(out) != 0 {
+ t.Error("ascii suffix negative")
+ }
+ out = getSuffixGlyphs(accent(testString), -3)
+ if len(out) != 0 {
+ t.Error("accent suffix negative")
+ }
+}