third_party: add github.com/peterh/liner

This go package provides gnu readline functionality (more or less).

Change-Id: I77998df261e321b6e2a0ecec4ca96af9802c8198
diff --git a/go/src/github.com/peterh/liner/COPYING b/go/src/github.com/peterh/liner/COPYING
new file mode 100644
index 0000000..9e8c9f2
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/LICENSE b/go/src/github.com/peterh/liner/LICENSE
new file mode 100644
index 0000000..9e8c9f2
--- /dev/null
+++ b/go/src/github.com/peterh/liner/LICENSE
@@ -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/go/src/github.com/peterh/liner/README.google b/go/src/github.com/peterh/liner/README.google
new file mode 100644
index 0000000..98ba360
--- /dev/null
+++ b/go/src/github.com/peterh/liner/README.google
@@ -0,0 +1,12 @@
+URL: https://github.com/peterh/liner/archive/1bb0d1c1a25ed393d8feb09bab039b2b1b1fbced.zip
+Version: 1bb0d1c1a25ed393d8feb09bab039b2b1b1fbced
+License: x11
+License File: LICENSE
+
+Description:
+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.
+
+Local Modifications:
+None.
diff --git a/go/src/github.com/peterh/liner/README.md b/go/src/github.com/peterh/liner/README.md
new file mode 100644
index 0000000..99027c6
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/bsdinput.go b/go/src/github.com/peterh/liner/bsdinput.go
new file mode 100644
index 0000000..4b552d4
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/common.go b/go/src/github.com/peterh/liner/common.go
new file mode 100644
index 0000000..9424abe
--- /dev/null
+++ b/go/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 || !s.terminalSupported {
+		fmt.Print(p)
+	}
+	linebuf, _, err := s.r.ReadLine()
+	if err != nil {
+		return "", err
+	}
+	return string(bytes.TrimSpace(linebuf)), nil
+}
diff --git a/go/src/github.com/peterh/liner/fallbackinput.go b/go/src/github.com/peterh/liner/fallbackinput.go
new file mode 100644
index 0000000..d9eb79d
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/input.go b/go/src/github.com/peterh/liner/input.go
new file mode 100644
index 0000000..94a8215
--- /dev/null
+++ b/go/src/github.com/peterh/liner/input.go
@@ -0,0 +1,362 @@
+// +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.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.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"))]
+}
diff --git a/go/src/github.com/peterh/liner/input_darwin.go b/go/src/github.com/peterh/liner/input_darwin.go
new file mode 100644
index 0000000..23c9c5d
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/input_linux.go b/go/src/github.com/peterh/liner/input_linux.go
new file mode 100644
index 0000000..6ca8712
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/input_test.go b/go/src/github.com/peterh/liner/input_test.go
new file mode 100644
index 0000000..e515a48
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/input_windows.go b/go/src/github.com/peterh/liner/input_windows.go
new file mode 100644
index 0000000..cc98719
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/line.go b/go/src/github.com/peterh/liner/line.go
new file mode 100644
index 0000000..87ce693
--- /dev/null
+++ b/go/src/github.com/peterh/liner/line.go
@@ -0,0 +1,862 @@
+// +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 returns a line of user input, not including a trailing
+// newline character. An io.EOF error is returned if the user signals end-of-file
+// by pressing Ctrl-D. Prompt allows line editing if the terminal supports it.
+func (s *State) Prompt(prompt string) (string, error) {
+	if s.inputRedirected || !s.terminalSupported {
+		return s.promptUnsupported(prompt)
+	}
+	if s.outputRedirected {
+		return "", ErrNotTerminalOutput
+	}
+
+	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.terminalSupported {
+		return "", errors.New("liner: function not supported in this terminal")
+	}
+	if s.inputRedirected {
+		return s.promptUnsupported(prompt)
+	}
+	if s.outputRedirected {
+		return "", ErrNotTerminalOutput
+	}
+
+	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/go/src/github.com/peterh/liner/line_test.go b/go/src/github.com/peterh/liner/line_test.go
new file mode 100644
index 0000000..727da6c
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/output.go b/go/src/github.com/peterh/liner/output.go
new file mode 100644
index 0000000..e91f4ea
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/output_windows.go b/go/src/github.com/peterh/liner/output_windows.go
new file mode 100644
index 0000000..27ae55a
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/prefix_test.go b/go/src/github.com/peterh/liner/prefix_test.go
new file mode 100644
index 0000000..c826d6c
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/race_test.go b/go/src/github.com/peterh/liner/race_test.go
new file mode 100644
index 0000000..e320849
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/signal.go b/go/src/github.com/peterh/liner/signal.go
new file mode 100644
index 0000000..0cba79e
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/signal_legacy.go b/go/src/github.com/peterh/liner/signal_legacy.go
new file mode 100644
index 0000000..fa3672d
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/unixmode.go b/go/src/github.com/peterh/liner/unixmode.go
new file mode 100644
index 0000000..9838923
--- /dev/null
+++ b/go/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/go/src/github.com/peterh/liner/width.go b/go/src/github.com/peterh/liner/width.go
new file mode 100644
index 0000000..5c6bf68
--- /dev/null
+++ b/go/src/github.com/peterh/liner/width.go
@@ -0,0 +1,60 @@
+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,
+}
+
+var doubleWidth = []*unicode.RangeTable{
+	unicode.Han,
+	unicode.Hangul,
+	unicode.Hiragana,
+	unicode.Katakana,
+}
+
+// countGlyphs considers zero-width characters to be zero glyphs wide,
+// and members of Chinese, Japanese, and Korean scripts to be 2 glyphs wide.
+func countGlyphs(s []rune) int {
+	n := 0
+	for _, r := range s {
+		switch {
+		case unicode.IsOneOf(zeroWidth, r):
+		case unicode.IsOneOf(doubleWidth, r):
+			n += 2
+		default:
+			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/go/src/github.com/peterh/liner/width_test.go b/go/src/github.com/peterh/liner/width_test.go
new file mode 100644
index 0000000..add779c
--- /dev/null
+++ b/go/src/github.com/peterh/liner/width_test.go
@@ -0,0 +1,102 @@
+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
+}
+
+type testCase struct {
+	s      []rune
+	glyphs int
+}
+
+var testCases = []testCase{
+	{[]rune("query"), 5},
+	{[]rune("私"), 2},
+	{[]rune("hello世界"), 9},
+}
+
+func TestCountGlyphs(t *testing.T) {
+	for _, testCase := range testCases {
+		count := countGlyphs(testCase.s)
+		if count != testCase.glyphs {
+			t.Errorf("ASCII count incorrect. %d != %d", count, testCase.glyphs)
+		}
+		count = countGlyphs(accent(testCase.s))
+		if count != testCase.glyphs {
+			t.Errorf("Accent count incorrect. %d != %d", count, testCase.glyphs)
+		}
+	}
+}
+
+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 _, testCase := range testCases {
+		for i := 0; i <= len(testCase.s); i++ {
+			iter := strconv.Itoa(i)
+			out := getPrefixGlyphs(testCase.s, i)
+			compare(out, testCase.s[:i], "ascii prefix "+iter, t)
+			out = getPrefixGlyphs(accent(testCase.s), i)
+			compare(out, accent(testCase.s[:i]), "accent prefix "+iter, t)
+		}
+		out := getPrefixGlyphs(testCase.s, 999)
+		compare(out, testCase.s, "ascii prefix overflow", t)
+		out = getPrefixGlyphs(accent(testCase.s), 999)
+		compare(out, accent(testCase.s), "accent prefix overflow", t)
+
+		out = getPrefixGlyphs(testCase.s, -3)
+		if len(out) != 0 {
+			t.Error("ascii prefix negative")
+		}
+		out = getPrefixGlyphs(accent(testCase.s), -3)
+		if len(out) != 0 {
+			t.Error("accent prefix negative")
+		}
+	}
+}
+
+func TestSuffixGlyphs(t *testing.T) {
+	for _, testCase := range testCases {
+		for i := 0; i <= len(testCase.s); i++ {
+			iter := strconv.Itoa(i)
+			out := getSuffixGlyphs(testCase.s, i)
+			compare(out, testCase.s[len(testCase.s)-i:], "ascii suffix "+iter, t)
+			out = getSuffixGlyphs(accent(testCase.s), i)
+			compare(out, accent(testCase.s[len(testCase.s)-i:]), "accent suffix "+iter, t)
+		}
+		out := getSuffixGlyphs(testCase.s, 999)
+		compare(out, testCase.s, "ascii suffix overflow", t)
+		out = getSuffixGlyphs(accent(testCase.s), 999)
+		compare(out, accent(testCase.s), "accent suffix overflow", t)
+
+		out = getSuffixGlyphs(testCase.s, -3)
+		if len(out) != 0 {
+			t.Error("ascii suffix negative")
+		}
+		out = getSuffixGlyphs(accent(testCase.s), -3)
+		if len(out) != 0 {
+			t.Error("accent suffix negative")
+		}
+	}
+}