support save history item

This commit is contained in:
Cheney 2015-09-22 18:16:24 +08:00
parent c8f8ec4b96
commit 3f23122fec
6 changed files with 97 additions and 33 deletions

View File

@ -17,7 +17,10 @@ bye: quit
} }
func main() { func main() {
l, err := readline.New("home -> ") l, err := readline.NewEx(&readline.Config{
Prompt: "home -> ",
HistoryFile: "/tmp/readline.tmp",
})
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -1,5 +1,12 @@
package readline package readline
import (
"bufio"
"container/list"
"os"
"strings"
)
type HisItem struct { type HisItem struct {
Source []rune Source []rune
Version int64 Version int64
@ -11,7 +18,47 @@ func (h *HisItem) Clean() {
h.Tmp = nil h.Tmp = nil
} }
func (o *Operation) showItem(obj interface{}) []rune { type opHistory struct {
path string
history *list.List
historyVer int64
current *list.Element
fd *os.File
}
func newOpHistory(path string) (o *opHistory) {
o = &opHistory{
path: path,
history: list.New(),
}
if o.path == "" {
return
}
f, err := os.OpenFile(o.path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
o.fd = f
r := bufio.NewReader(o.fd)
for {
line, err := r.ReadSlice('\n')
if err != nil {
break
}
o.PushHistory([]rune(strings.TrimSpace(string(line))))
}
o.historyVer++
o.PushHistory(nil)
return
}
func (o *opHistory) Close() {
if o.fd != nil {
o.fd.Close()
}
}
func (o *opHistory) showItem(obj interface{}) []rune {
item := obj.(*HisItem) item := obj.(*HisItem)
if item.Version == o.historyVer { if item.Version == o.historyVer {
return item.Tmp return item.Tmp
@ -19,7 +66,7 @@ func (o *Operation) showItem(obj interface{}) []rune {
return item.Source return item.Source
} }
func (o *Operation) PrevHistory() []rune { func (o *opHistory) PrevHistory() []rune {
if o.current == nil { if o.current == nil {
return nil return nil
} }
@ -31,7 +78,7 @@ func (o *Operation) PrevHistory() []rune {
return o.showItem(current.Value) return o.showItem(current.Value)
} }
func (o *Operation) NextHistory() ([]rune, bool) { func (o *opHistory) NextHistory() ([]rune, bool) {
if o.current == nil { if o.current == nil {
return nil, false return nil, false
} }
@ -44,7 +91,7 @@ func (o *Operation) NextHistory() ([]rune, bool) {
return o.showItem(current.Value), true return o.showItem(current.Value), true
} }
func (o *Operation) NewHistory(current []rune) { func (o *opHistory) NewHistory(current []rune) {
// if just use last command without modify // if just use last command without modify
// just clean lastest history // just clean lastest history
if back := o.history.Back(); back != nil { if back := o.history.Back(); back != nil {
@ -82,7 +129,7 @@ func (o *Operation) NewHistory(current []rune) {
o.PushHistory(nil) o.PushHistory(nil)
} }
func (o *Operation) UpdateHistory(s []rune, commit bool) { func (o *opHistory) UpdateHistory(s []rune, commit bool) {
if o.current == nil { if o.current == nil {
o.PushHistory(s) o.PushHistory(s)
return return
@ -92,13 +139,16 @@ func (o *Operation) UpdateHistory(s []rune, commit bool) {
if commit { if commit {
r.Source = make([]rune, len(s)) r.Source = make([]rune, len(s))
copy(r.Source, s) copy(r.Source, s)
if o.fd != nil {
o.fd.Write([]byte(string(r.Source) + "\n"))
}
} else { } else {
r.Tmp = append(r.Tmp[:0], s...) r.Tmp = append(r.Tmp[:0], s...)
} }
o.current.Value = r o.current.Value = r
} }
func (o *Operation) PushHistory(s []rune) { func (o *opHistory) PushHistory(s []rune) {
// copy // copy
newCopy := make([]rune, len(s)) newCopy := make([]rune, len(s))
copy(newCopy, s) copy(newCopy, s)

View File

@ -1,20 +1,16 @@
package readline package readline
import ( import (
"container/list"
"io" "io"
"os" "os"
) )
type Operation struct { type Operation struct {
r *os.File cfg *Config
t *Terminal t *Terminal
buf *RuneBuffer buf *RuneBuffer
outchan chan []rune outchan chan []rune
*opHistory
history *list.List
historyVer int64
current *list.Element
} }
const ( const (
@ -43,13 +39,13 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
return n, err return n, err
} }
func NewOperation(r *os.File, t *Terminal, prompt string) *Operation { func NewOperation(t *Terminal, cfg *Config) *Operation {
op := &Operation{ op := &Operation{
r: r, cfg: cfg,
t: t, t: t,
buf: NewRuneBuffer(t, prompt), buf: NewRuneBuffer(t, cfg.Prompt),
outchan: make(chan []rune), outchan: make(chan []rune),
history: list.New(), opHistory: newOpHistory(cfg.HistoryFile),
} }
go op.ioloop() go op.ioloop()
return op return op
@ -136,3 +132,7 @@ func (l *Operation) Slice() ([]byte, error) {
} }
return []byte(string(r)), nil return []byte(string(r)), nil
} }
func (l *Operation) Close() {
l.opHistory.Close()
}

View File

@ -7,18 +7,27 @@ type Instance struct {
o *Operation o *Operation
} }
func New(prompt string) (*Instance, error) { type Config struct {
t, err := NewTerminal() Prompt string
HistoryFile string
}
func NewEx(cfg *Config) (*Instance, error) {
t, err := NewTerminal(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rl := t.Readline(prompt) rl := t.Readline()
return &Instance{ return &Instance{
t: t, t: t,
o: rl, o: rl,
}, nil }, nil
} }
func New(prompt string) (*Instance, error) {
return NewEx(&Config{Prompt: prompt})
}
func (i *Instance) Stderr() io.Writer { func (i *Instance) Stderr() io.Writer {
return i.o.Stderr() return i.o.Stderr()
} }
@ -32,5 +41,6 @@ func (i *Instance) ReadSlice() ([]byte, error) {
} }
func (i *Instance) Close() error { func (i *Instance) Close() error {
i.o.Close()
return i.t.Close() return i.t.Close()
} }

View File

@ -6,12 +6,10 @@ import (
) )
type RuneBuffer struct { type RuneBuffer struct {
buf []rune buf []rune
idx int idx int
prompt []byte prompt []byte
w io.Writer w io.Writer
hasPrompt bool
lastWritten int
} }
func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer { func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
@ -187,7 +185,9 @@ func (r *RuneBuffer) Output() []byte {
func (r *RuneBuffer) CleanOutput() []byte { func (r *RuneBuffer) CleanOutput() []byte {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
buf.Write([]byte("\033[J")) buf.Write([]byte("\033[J")) // just like ^k :)
// TODO: calculate how many line before cursor.
for i := 0; i <= 100; i++ { for i := 0; i <= 100; i++ {
buf.WriteString("\033[2K\r\b") buf.WriteString("\033[2K\r\b")
} }
@ -202,7 +202,6 @@ func (r *RuneBuffer) Reset() []rune {
ret := r.buf ret := r.buf
r.buf = r.buf[:0] r.buf = r.buf[:0]
r.idx = 0 r.idx = 0
r.hasPrompt = false
return ret return ret
} }

View File

@ -27,17 +27,19 @@ const (
) )
type Terminal struct { type Terminal struct {
cfg *Config
state *terminal.State state *terminal.State
outchan chan rune outchan chan rune
closed int64 closed int64
} }
func NewTerminal() (*Terminal, error) { func NewTerminal(cfg *Config) (*Terminal, error) {
state, err := MakeRaw(syscall.Stdin) state, err := MakeRaw(syscall.Stdin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t := &Terminal{ t := &Terminal{
cfg: cfg,
state: state, state: state,
outchan: make(chan rune), outchan: make(chan rune),
} }
@ -58,8 +60,8 @@ func (t *Terminal) PrintRune(r rune) {
fmt.Fprintf(os.Stdout, "%c", r) fmt.Fprintf(os.Stdout, "%c", r)
} }
func (t *Terminal) Readline(prompt string) *Operation { func (t *Terminal) Readline() *Operation {
return NewOperation(os.Stdin, t, prompt) return NewOperation(t, t.cfg)
} }
func (t *Terminal) ReadRune() rune { func (t *Terminal) ReadRune() rune {