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() {
l, err := readline.New("home -> ")
l, err := readline.NewEx(&readline.Config{
Prompt: "home -> ",
HistoryFile: "/tmp/readline.tmp",
})
if err != nil {
panic(err)
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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 {