init commit

This commit is contained in:
Cheney 2015-09-20 23:14:29 +08:00
parent 71b78bbfa7
commit 740e90a464
6 changed files with 486 additions and 0 deletions

BIN
example/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

42
example/main.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"io"
"log"
"strconv"
"time"
"github.com/chzyer/readline"
)
func main() {
t, err := readline.NewTerminal()
if err != nil {
panic(err)
}
defer t.Close()
l := t.NewReadline("> ")
log.SetOutput(l.Stderr())
for {
line, err := l.Readline()
if err != nil {
break
}
switch line {
case "help":
io.WriteString(l.Stderr(), "sayhello: start to display oneline log per second\nbye: quit\n")
case "sayhello":
go func() {
for _ = range time.Tick(time.Second) {
log.Println("hello")
}
}()
case "bye":
goto exit
default:
log.Println("you said:", strconv.Quote(line))
}
}
exit:
}

103
readline.go Normal file
View File

@ -0,0 +1,103 @@
package readline
import (
"io"
"os"
)
type Readline struct {
r *os.File
t *Terminal
buf *RuneBuffer
outchan chan []rune
}
const (
CharLineStart = 0x1
CharLineEnd = 0x5
CharPrev = 0x2
CharNext = 0x6
CharEscape = 0x7f
CharEnter = 0xd
)
type wrapWriter struct {
r *Readline
target io.Writer
}
func (w *wrapWriter) Write(b []byte) (int, error) {
buf := w.r.buf
buf.Clean()
n, err := w.target.Write(b)
w.r.buf.RefreshSet(0, 0)
return n, err
}
func newReadline(r *os.File, t *Terminal, prompt string) *Readline {
rl := &Readline{
r: r,
t: t,
buf: NewRuneBuffer(t, prompt),
outchan: make(chan []rune),
}
go rl.ioloop()
return rl
}
func (l *Readline) ioloop() {
for {
r := l.t.ReadRune()
switch r {
case MetaNext:
l.buf.MoveToNextWord()
case MetaPrev:
l.buf.MoveToPrevWord()
case MetaDelete:
l.buf.DeleteWord()
case CharLineStart:
l.buf.MoveToLineStart()
case CharLineEnd:
l.buf.MoveToLineEnd()
case KeyDelete:
l.buf.Delete()
case CharEscape:
l.buf.BackEscape()
case CharEnter:
l.buf.WriteRune('\n')
data := l.buf.Reset()
l.outchan <- data[:len(data)-1]
case CharPrev:
l.buf.MovePrev()
case CharNext:
l.buf.MoveNext()
case KeyInterrupt:
l.buf.WriteString("^C\n")
l.outchan <- nil
break
default:
l.buf.WriteRune(r)
}
}
}
func (l *Readline) Stderr() io.Writer {
return &wrapWriter{target: os.Stderr, r: l}
}
func (l *Readline) Readline() (string, error) {
r, err := l.ReadlineSlice()
if err != nil {
return "", err
}
return string(r), nil
}
func (l *Readline) ReadlineSlice() ([]byte, error) {
l.buf.Refresh(0, 0)
r := <-l.outchan
if r == nil {
return nil, io.EOF
}
return []byte(string(r)), nil
}

189
runebuf.go Normal file
View File

@ -0,0 +1,189 @@
package readline
import (
"bytes"
"io"
)
type RuneBuffer struct {
buf []rune
idx int
prompt []byte
w io.Writer
lastWritten int
printPrompt bool
}
func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
rb := &RuneBuffer{
prompt: []byte(prompt),
w: w,
printPrompt: true,
}
return rb
}
func (r *RuneBuffer) Runes() []rune {
return r.buf
}
func (r *RuneBuffer) Pos() int {
return r.idx
}
func (r *RuneBuffer) Len() int {
return len(r.buf)
}
func (r *RuneBuffer) MoveToLineStart() {
if r.idx == 0 {
return
}
r.Refresh(-1, r.SetIdx(0))
}
func (r *RuneBuffer) MovePrev() {
if r.idx == 0 {
return
}
r.idx--
r.Refresh(0, -1)
}
func (rb *RuneBuffer) WriteString(s string) {
rb.WriteRunes([]rune(s))
}
func (rb *RuneBuffer) WriteRune(r rune) {
rb.WriteRunes([]rune{r})
}
func (rb *RuneBuffer) WriteRunes(r []rune) {
tail := append(r, rb.buf[rb.idx:]...)
rb.buf = append(rb.buf[:rb.idx], tail...)
rb.idx++
rb.Refresh(1, 1)
}
func (r *RuneBuffer) MoveNext() {
if r.idx == len(r.buf) {
return
}
r.idx++
r.Refresh(0, 1)
}
func (r *RuneBuffer) Delete() {
if r.idx == len(r.buf) {
return
}
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
r.Refresh(-1, 0)
}
func (r *RuneBuffer) DeleteWord() {
if r.idx == len(r.buf) {
return
}
for i := r.idx + 1; i < len(r.buf); i++ {
if r.buf[i] != ' ' && r.buf[i-1] == ' ' {
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
r.Refresh(r.idx-i+1, 0)
return
}
}
length := len(r.buf)
r.buf = r.buf[:r.idx]
r.Refresh(length-r.idx, 0)
}
func (r *RuneBuffer) MoveToPrevWord() {
if r.idx == 0 {
return
}
for i := r.idx - 1; i > 0; i-- {
if r.buf[i] != ' ' && r.buf[i-1] == ' ' {
r.Refresh(0, r.SetIdx(i))
return
}
}
r.Refresh(0, r.SetIdx(0))
}
func (r *RuneBuffer) SetIdx(idx int) (change int) {
i := r.idx
r.idx = idx
return r.idx - i
}
func (r *RuneBuffer) MoveToNextWord() {
for i := r.idx + 1; i < len(r.buf); i++ {
if r.buf[i] != ' ' && r.buf[i-1] == ' ' {
r.Refresh(0, r.SetIdx(i))
return
}
}
r.Refresh(0, r.SetIdx(len(r.buf)))
}
func (r *RuneBuffer) BackEscape() {
if r.idx == 0 {
return
}
r.idx--
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
r.Refresh(-1, -1)
}
func (r *RuneBuffer) MoveToLineEnd() {
if r.idx == len(r.buf) {
return
}
r.Refresh(0, r.SetIdx(len(r.buf)))
}
func (r *RuneBuffer) Refresh(chlen, chidx int) {
s := r.Output(len(r.buf)-chlen, r.idx-chidx)
r.w.Write(s)
}
func (r *RuneBuffer) RefreshSet(originLength, originIdx int) {
r.w.Write(r.Output(originLength, originIdx))
}
func (r *RuneBuffer) Output(originLength, originIdx int) []byte {
buf := bytes.NewBuffer(nil)
if r.printPrompt {
r.printPrompt = false
buf.Write(r.prompt)
}
buf.Write(bytes.Repeat([]byte{'\b'}, originIdx))
buf.Write([]byte(string(r.buf)))
if originLength > len(r.buf) {
buf.Write(bytes.Repeat([]byte{' '}, originLength-len(r.buf)))
buf.Write(bytes.Repeat([]byte{'\b'}, originLength-len(r.buf)))
}
buf.Write(bytes.Repeat([]byte{'\b'}, len(r.buf)-r.idx))
return buf.Bytes()
}
func (r *RuneBuffer) Clean() {
moveToFirst := r.idx
moveToFirst += len(r.prompt)
r.w.Write(bytes.Repeat([]byte{'\b'}, moveToFirst))
length := len(r.buf) + len(r.prompt)
r.w.Write(bytes.Repeat([]byte{' '}, length))
r.w.Write(bytes.Repeat([]byte{'\b'}, length))
r.printPrompt = true
}
func (r *RuneBuffer) Reset() []rune {
ret := r.buf
r.buf = r.buf[:0]
r.idx = 0
r.printPrompt = true
r.Refresh(-len(ret), r.SetIdx(0))
return ret
}

108
terminal.go Normal file
View File

@ -0,0 +1,108 @@
package readline
import (
"bufio"
"fmt"
"os"
"sync/atomic"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
const (
MetaPrev = -iota - 1
MetaNext
MetaDelete
)
const (
KeyPrevChar = 0x2
KeyInterrupt = 0x3
KeyNextChar = 0x6
KeyDelete = 0x4
KeyEnter = 0xd
KeyEsc = 0x1b
)
type Terminal struct {
state *terminal.State
outchan chan rune
closed int64
}
func NewTerminal() (*Terminal, error) {
state, err := MakeRaw(syscall.Stdin)
if err != nil {
return nil, err
}
t := &Terminal{
state: state,
outchan: make(chan rune),
}
go t.ioloop()
return t, nil
}
func (t *Terminal) Write(b []byte) (int, error) {
return os.Stdout.Write(b)
}
func (t *Terminal) Print(s string) {
fmt.Fprintf(os.Stdout, "%s", s)
}
func (t *Terminal) PrintRune(r rune) {
fmt.Fprintf(os.Stdout, "%c", r)
}
func (t *Terminal) NewReadline(prompt string) *Readline {
return newReadline(os.Stdin, t, prompt)
}
func (t *Terminal) ReadRune() rune {
return <-t.outchan
}
func (t *Terminal) ioloop() {
buf := bufio.NewReader(os.Stdin)
prefix := false
for {
r, _, err := buf.ReadRune()
if err != nil {
break
}
if prefix {
prefix = false
r = prefixKey(r)
}
if IsPrintable(r) || r < 0 {
t.outchan <- r
continue
}
switch r {
case KeyInterrupt:
t.outchan <- r
goto exit
case KeyEsc:
prefix = true
case KeyEnter, KeyPrevChar, KeyNextChar, KeyDelete:
fallthrough
case CharLineEnd, CharLineStart:
t.outchan <- r
default:
println("np:", r)
}
}
exit:
}
func (t *Terminal) Close() error {
if atomic.SwapInt64(&t.closed, 1) != 0 {
return nil
}
return Restore(syscall.Stdin, t.state)
}

44
utils.go Normal file
View File

@ -0,0 +1,44 @@
package readline
import (
"fmt"
"os"
"golang.org/x/crypto/ssh/terminal"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
return terminal.IsTerminal(fd)
}
func MakeRaw(fd int) (*terminal.State, error) {
return terminal.MakeRaw(fd)
}
func Restore(fd int, state *terminal.State) error {
return terminal.Restore(fd, state)
}
func IsPrintable(key rune) bool {
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
return key >= 32 && !isInSurrogateArea
}
func prefixKey(r rune) rune {
switch r {
case 'b':
r = MetaPrev
case 'f':
r = MetaNext
case 'd':
r = MetaDelete
}
return r
}
func Debug(o ...interface{}) {
f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
fmt.Fprintln(f, o...)
f.Close()
}