diff --git a/example/main.go b/example/main.go index 995456b..1860740 100644 --- a/example/main.go +++ b/example/main.go @@ -10,13 +10,12 @@ import ( ) func main() { - t, err := readline.NewTerminal() + l, err := readline.New("> ") if err != nil { panic(err) } - defer t.Close() + defer l.Close() - l := t.NewReadline("> ") log.SetOutput(l.Stderr()) for { line, err := l.Readline() diff --git a/history.go b/history.go index a58c89a..b6e5d46 100644 --- a/history.go +++ b/history.go @@ -1,56 +1,56 @@ package readline -func (l *Readline) PrevHistory() []rune { - if l.current == nil { +func (o *Operation) PrevHistory() []rune { + if o.current == nil { return nil } - current := l.current.Prev() + current := o.current.Prev() if current == nil { return nil } - l.current = current + o.current = current return current.Value.([]rune) } -func (l *Readline) NextHistory() []rune { - if l.current == nil { +func (o *Operation) NextHistory() []rune { + if o.current == nil { return nil } - current := l.current.Next() + current := o.current.Next() if current == nil { return nil } - l.current = current + o.current = current return current.Value.([]rune) } -func (l *Readline) NewHistory(current []rune) { - l.UpdateHistory(current) - if l.current != l.history.Back() { +func (o *Operation) NewHistory(current []rune) { + o.UpdateHistory(current) + if o.current != o.history.Back() { // move history item to current command - l.history.Remove(l.current) - use := l.current.Value.([]rune) - l.current = l.history.Back() - l.UpdateHistory(use) + o.history.Remove(o.current) + use := o.current.Value.([]rune) + o.current = o.history.Back() + o.UpdateHistory(use) } // push a new one to commit current command - l.PushHistory(nil) + o.PushHistory(nil) } -func (l *Readline) UpdateHistory(s []rune) { - if l.current == nil { - l.PushHistory(s) +func (o *Operation) UpdateHistory(s []rune) { + if o.current == nil { + o.PushHistory(s) return } - r := l.current.Value.([]rune) - l.current.Value = append(r[:0], s...) + r := o.current.Value.([]rune) + o.current.Value = append(r[:0], s...) } -func (l *Readline) PushHistory(s []rune) { +func (o *Operation) PushHistory(s []rune) { // copy newCopy := make([]rune, len(s)) copy(newCopy, s) - elem := l.history.PushBack(newCopy) - l.current = elem + elem := o.history.PushBack(newCopy) + o.current = elem } diff --git a/operation.go b/operation.go new file mode 100644 index 0000000..892d265 --- /dev/null +++ b/operation.go @@ -0,0 +1,131 @@ +package readline + +import ( + "container/list" + "io" + "os" +) + +type Operation struct { + r *os.File + t *Terminal + buf *RuneBuffer + outchan chan []rune + + history *list.List + current *list.Element +} + +const ( + CharLineStart = 0x1 + CharLineEnd = 0x5 + CharNext = 0xe + CharPrev = 0x10 + CharBackward = 0x2 + CharForward = 0x6 + CharEscape = 0x7f + CharEnter = 0xd + CharEnter2 = 0xa +) + +type wrapWriter struct { + r *Operation + 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 NewOperation(r *os.File, t *Terminal, prompt string) *Operation { + op := &Operation{ + r: r, + t: t, + buf: NewRuneBuffer(t, prompt), + outchan: make(chan []rune), + history: list.New(), + } + go op.ioloop() + return op +} + +func (l *Operation) 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, CharEnter2: + l.buf.WriteRune('\n') + data := l.buf.Reset() + data = data[:len(data)-1] // trim \n + l.outchan <- data + l.NewHistory(data) + case CharBackward: + l.buf.MoveBackward() + case CharForward: + l.buf.MoveForward() + case CharPrev: + buf := l.PrevHistory() + if buf != nil { + l.buf.Set(buf) + } + case CharNext: + buf := l.NextHistory() + if buf != nil { + l.buf.Set(buf) + } + case KeyInterrupt: + l.buf.WriteString("^C\n") + l.outchan <- nil + default: + l.buf.WriteRune(r) + } + l.UpdateHistory(l.buf.Runes()) + } +} + +func (l *Operation) Stderr() io.Writer { + return &wrapWriter{target: os.Stderr, r: l} +} + +func (l *Operation) String() (string, error) { + r, err := l.Runes() + if err != nil { + return "", err + } + return string(r), nil +} + +func (l *Operation) Runes() ([]rune, error) { + l.buf.Refresh(0, 0) // print prompt + r := <-l.outchan + if r == nil { + return nil, io.EOF + } + return r, nil +} + +func (l *Operation) Slice() ([]byte, error) { + r, err := l.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} diff --git a/readline.go b/readline.go index 63c0521..d87c6bd 100644 --- a/readline.go +++ b/readline.go @@ -1,123 +1,36 @@ package readline -import ( - "container/list" - "io" - "os" -) +import "io" -type Readline struct { - r *os.File - t *Terminal - buf *RuneBuffer - outchan chan []rune - - history *list.List - current *list.Element +type Instance struct { + t *Terminal + o *Operation } -const ( - CharLineStart = 0x1 - CharLineEnd = 0x5 - CharNext = 0xe - CharPrev = 0x10 - CharBackward = 0x2 - CharForward = 0x6 - CharEscape = 0x7f - CharEnter = 0xd - CharEnter2 = 0xa -) - -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), - history: list.New(), - } - 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, CharEnter2: - l.buf.WriteRune('\n') - data := l.buf.Reset() - data = data[:len(data)-1] // trim \n - l.outchan <- data - l.NewHistory(data) - case CharBackward: - l.buf.MoveBackward() - case CharForward: - l.buf.MoveForward() - case CharPrev: - buf := l.PrevHistory() - if buf != nil { - l.buf.Set(buf) - } - case CharNext: - buf := l.NextHistory() - if buf != nil { - l.buf.Set(buf) - } - case KeyInterrupt: - l.buf.WriteString("^C\n") - l.outchan <- nil - default: - l.buf.WriteRune(r) - } - l.UpdateHistory(l.buf.Runes()) - } -} - -func (l *Readline) Stderr() io.Writer { - return &wrapWriter{target: os.Stderr, r: l} -} - -func (l *Readline) Readline() (string, error) { - r, err := l.ReadlineSlice() +func New(prompt string) (*Instance, error) { + t, err := NewTerminal() if err != nil { - return "", err + return nil, err } - return string(r), nil + rl := t.Readline(prompt) + return &Instance{ + t: t, + o: rl, + }, 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 +func (i *Instance) Stderr() io.Writer { + return i.o.Stderr() +} + +func (i *Instance) Readline() (string, error) { + return i.o.String() +} + +func (i *Instance) ReadSlice() ([]byte, error) { + return i.o.Slice() +} + +func (i *Instance) Close() error { + return i.t.Close() } diff --git a/terminal.go b/terminal.go index ef5bc6e..302c63b 100644 --- a/terminal.go +++ b/terminal.go @@ -56,8 +56,8 @@ 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) Readline(prompt string) *Operation { + return NewOperation(os.Stdin, t, prompt) } func (t *Terminal) ReadRune() rune {