diff --git a/example/main.go b/example/main.go index 715dbba..1b8865a 100644 --- a/example/main.go +++ b/example/main.go @@ -17,7 +17,10 @@ bye: quit } func main() { - l, err := readline.New("home -> ") + l, err := readline.NewEx(&readline.Config{ + Prompt: "home -> ", + HistoryFile: "/tmp/readline.tmp", + }) if err != nil { panic(err) } diff --git a/history.go b/history.go index e5b488a..9a0a331 100644 --- a/history.go +++ b/history.go @@ -1,5 +1,12 @@ package readline +import ( + "bufio" + "container/list" + "os" + "strings" +) + type HisItem struct { Source []rune Version int64 @@ -11,7 +18,47 @@ func (h *HisItem) Clean() { 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) if item.Version == o.historyVer { return item.Tmp @@ -19,7 +66,7 @@ func (o *Operation) showItem(obj interface{}) []rune { return item.Source } -func (o *Operation) PrevHistory() []rune { +func (o *opHistory) PrevHistory() []rune { if o.current == nil { return nil } @@ -31,7 +78,7 @@ func (o *Operation) PrevHistory() []rune { return o.showItem(current.Value) } -func (o *Operation) NextHistory() ([]rune, bool) { +func (o *opHistory) NextHistory() ([]rune, bool) { if o.current == nil { return nil, false } @@ -44,7 +91,7 @@ func (o *Operation) NextHistory() ([]rune, bool) { 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 // just clean lastest history if back := o.history.Back(); back != nil { @@ -82,7 +129,7 @@ func (o *Operation) NewHistory(current []rune) { o.PushHistory(nil) } -func (o *Operation) UpdateHistory(s []rune, commit bool) { +func (o *opHistory) UpdateHistory(s []rune, commit bool) { if o.current == nil { o.PushHistory(s) return @@ -92,13 +139,16 @@ func (o *Operation) UpdateHistory(s []rune, commit bool) { if commit { r.Source = make([]rune, len(s)) copy(r.Source, s) + if o.fd != nil { + o.fd.Write([]byte(string(r.Source) + "\n")) + } } else { r.Tmp = append(r.Tmp[:0], s...) } o.current.Value = r } -func (o *Operation) PushHistory(s []rune) { +func (o *opHistory) PushHistory(s []rune) { // copy newCopy := make([]rune, len(s)) copy(newCopy, s) diff --git a/operation.go b/operation.go index c7041cd..0a17a71 100644 --- a/operation.go +++ b/operation.go @@ -1,20 +1,16 @@ package readline import ( - "container/list" "io" "os" ) type Operation struct { - r *os.File + cfg *Config t *Terminal buf *RuneBuffer outchan chan []rune - - history *list.List - historyVer int64 - current *list.Element + *opHistory } const ( @@ -43,13 +39,13 @@ func (w *wrapWriter) Write(b []byte) (int, error) { return n, err } -func NewOperation(r *os.File, t *Terminal, prompt string) *Operation { +func NewOperation(t *Terminal, cfg *Config) *Operation { op := &Operation{ - r: r, - t: t, - buf: NewRuneBuffer(t, prompt), - outchan: make(chan []rune), - history: list.New(), + cfg: cfg, + t: t, + buf: NewRuneBuffer(t, cfg.Prompt), + outchan: make(chan []rune), + opHistory: newOpHistory(cfg.HistoryFile), } go op.ioloop() return op @@ -136,3 +132,7 @@ func (l *Operation) Slice() ([]byte, error) { } return []byte(string(r)), nil } + +func (l *Operation) Close() { + l.opHistory.Close() +} diff --git a/readline.go b/readline.go index d87c6bd..adf87bc 100644 --- a/readline.go +++ b/readline.go @@ -7,18 +7,27 @@ type Instance struct { o *Operation } -func New(prompt string) (*Instance, error) { - t, err := NewTerminal() +type Config struct { + Prompt string + HistoryFile string +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) if err != nil { return nil, err } - rl := t.Readline(prompt) + rl := t.Readline() return &Instance{ t: t, o: rl, }, nil } +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + func (i *Instance) Stderr() io.Writer { return i.o.Stderr() } @@ -32,5 +41,6 @@ func (i *Instance) ReadSlice() ([]byte, error) { } func (i *Instance) Close() error { + i.o.Close() return i.t.Close() } diff --git a/runebuf.go b/runebuf.go index cae7685..d3d4cbd 100644 --- a/runebuf.go +++ b/runebuf.go @@ -6,12 +6,10 @@ import ( ) type RuneBuffer struct { - buf []rune - idx int - prompt []byte - w io.Writer - hasPrompt bool - lastWritten int + buf []rune + idx int + prompt []byte + w io.Writer } func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer { @@ -187,7 +185,9 @@ func (r *RuneBuffer) Output() []byte { func (r *RuneBuffer) CleanOutput() []byte { 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++ { buf.WriteString("\033[2K\r\b") } @@ -202,7 +202,6 @@ func (r *RuneBuffer) Reset() []rune { ret := r.buf r.buf = r.buf[:0] r.idx = 0 - r.hasPrompt = false return ret } diff --git a/terminal.go b/terminal.go index 430f993..1204be5 100644 --- a/terminal.go +++ b/terminal.go @@ -27,17 +27,19 @@ const ( ) type Terminal struct { + cfg *Config state *terminal.State outchan chan rune closed int64 } -func NewTerminal() (*Terminal, error) { +func NewTerminal(cfg *Config) (*Terminal, error) { state, err := MakeRaw(syscall.Stdin) if err != nil { return nil, err } t := &Terminal{ + cfg: cfg, state: state, outchan: make(chan rune), } @@ -58,8 +60,8 @@ func (t *Terminal) PrintRune(r rune) { fmt.Fprintf(os.Stdout, "%c", r) } -func (t *Terminal) Readline(prompt string) *Operation { - return NewOperation(os.Stdin, t, prompt) +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) } func (t *Terminal) ReadRune() rune {