add history limitation

This commit is contained in:
Cheney 2015-10-04 15:23:00 +08:00
parent 4dc2ce7141
commit bfa8c1dfdb
3 changed files with 93 additions and 20 deletions

View File

@ -19,40 +19,81 @@ func (h *hisItem) Clean() {
} }
type opHistory struct { type opHistory struct {
path string cfg *Config
history *list.List history *list.List
historyVer int64 historyVer int64
current *list.Element current *list.Element
fd *os.File fd *os.File
} }
func newOpHistory(path string) (o *opHistory) { func newOpHistory(cfg *Config) (o *opHistory) {
o = &opHistory{ o = &opHistory{
path: path, cfg: cfg,
history: list.New(), history: list.New(),
} }
if o.path == "" { if o.cfg.HistoryFile != "" {
return o.historyUpdatePath(o.cfg.HistoryFile)
} }
f, err := os.OpenFile(o.path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) return
}
// only called by newOpHistory
func (o *opHistory) historyUpdatePath(path string) {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
return return
} }
o.fd = f o.fd = f
r := bufio.NewReader(o.fd) r := bufio.NewReader(o.fd)
for { total := 0
for ; ; total++ {
line, err := r.ReadSlice('\n') line, err := r.ReadSlice('\n')
if err != nil { if err != nil {
break break
} }
o.PushHistory([]rune(strings.TrimSpace(string(line)))) o.PushHistory([]rune(strings.TrimSpace(string(line))))
o.CompactHistory()
}
if total > o.cfg.HistoryLimit {
o.HistoryRewrite()
} }
o.historyVer++ o.historyVer++
o.PushHistory(nil) o.PushHistory(nil)
return return
} }
func (o *opHistory) Close() { func (o *opHistory) CompactHistory() {
for o.history.Len() > o.cfg.HistoryLimit {
o.history.Remove(o.history.Front())
}
}
func (o *opHistory) HistoryRewrite() {
if o.cfg.HistoryFile == "" {
return
}
tmpFile := o.cfg.HistoryFile + ".tmp"
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
if err != nil {
return
}
defer fd.Close()
buf := bufio.NewWriter(fd)
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
buf.WriteString(string(elem.Value.(*hisItem).Source))
}
buf.Flush()
if o.fd != nil {
o.fd.Close()
}
// fd is write only, just satisfy what we need.
o.fd = fd
}
func (o *opHistory) CloseHistory() {
if o.fd != nil { if o.fd != nil {
o.fd.Close() o.fd.Close()
} }
@ -197,7 +238,6 @@ func (o *opHistory) UpdateHistory(s []rune, commit bool) {
} }
func (o *opHistory) PushHistory(s []rune) { func (o *opHistory) PushHistory(s []rune) {
// copy
newCopy := make([]rune, len(s)) newCopy := make([]rune, len(s))
copy(newCopy, s) copy(newCopy, s)
elem := o.history.PushBack(&hisItem{Source: newCopy}) elem := o.history.PushBack(&hisItem{Source: newCopy})

View File

@ -55,8 +55,8 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
t: t, t: t,
buf: NewRuneBuffer(t, cfg.Prompt), buf: NewRuneBuffer(t, cfg.Prompt),
outchan: make(chan []rune), outchan: make(chan []rune),
opHistory: newOpHistory(cfg.HistoryFile),
} }
op.SetHistoryPath(cfg.HistoryFile)
op.opVim = newVimMode(op) op.opVim = newVimMode(op)
op.w = op.buf.w op.w = op.buf.w
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory) op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
@ -286,5 +286,13 @@ func (o *Operation) Slice() ([]byte, error) {
} }
func (o *Operation) Close() { func (o *Operation) Close() {
o.opHistory.Close() o.opHistory.CloseHistory()
}
func (o *Operation) SetHistoryPath(path string) {
if o.opHistory != nil {
o.opHistory.CloseHistory()
}
o.cfg.HistoryFile = path
o.opHistory = newOpHistory(o.cfg)
} }

View File

@ -3,15 +3,26 @@ package readline
import "io" import "io"
type Instance struct { type Instance struct {
Config *Config
Terminal *Terminal Terminal *Terminal
Operation *Operation Operation *Operation
} }
type Config struct { type Config struct {
// prompt supports ANSI escape sequence, so we can color some characters even in windows
Prompt string Prompt string
// readline will persist historys to file where HistoryFile specified
HistoryFile string HistoryFile string
// specify the max length of historys, it's 500 by default
HistoryLimit int
// AutoCompleter will called once user press TAB
AutoComplete AutoCompleter AutoComplete AutoCompleter
// If VimMode is true, readline will in vim.insert mode by default
VimMode bool VimMode bool
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
@ -29,6 +40,9 @@ func (c *Config) Init() error {
if c.Stderr == nil { if c.Stderr == nil {
c.Stderr = Stderr c.Stderr = Stderr
} }
if c.HistoryLimit < 0 {
c.HistoryLimit = 500
}
return nil return nil
} }
@ -39,6 +53,7 @@ func NewEx(cfg *Config) (*Instance, error) {
} }
rl := t.Readline() rl := t.Readline()
return &Instance{ return &Instance{
Config: cfg,
Terminal: t, Terminal: t,
Operation: rl, Operation: rl,
}, nil }, nil
@ -52,14 +67,22 @@ func (i *Instance) SetPrompt(s string) {
i.Operation.SetPrompt(s) i.Operation.SetPrompt(s)
} }
// change hisotry persistence in runtime
func (i *Instance) SetHistoryPath(p string) {
i.Operation.SetHistoryPath(p)
}
// readline will refresh automatic when write through Stdout()
func (i *Instance) Stdout() io.Writer { func (i *Instance) Stdout() io.Writer {
return i.Operation.Stdout() return i.Operation.Stdout()
} }
// readline will refresh automatic when write through Stdout()
func (i *Instance) Stderr() io.Writer { func (i *Instance) Stderr() io.Writer {
return i.Operation.Stderr() return i.Operation.Stderr()
} }
// switch VimMode in runtime
func (i *Instance) SetVimMode(on bool) { func (i *Instance) SetVimMode(on bool) {
i.Operation.SetVimMode(on) i.Operation.SetVimMode(on)
} }
@ -76,10 +99,12 @@ func (i *Instance) Readline() (string, error) {
return i.Operation.String() return i.Operation.String()
} }
// same as readline
func (i *Instance) ReadSlice() ([]byte, error) { func (i *Instance) ReadSlice() ([]byte, error) {
return i.Operation.Slice() return i.Operation.Slice()
} }
// we must make sure that call Close() before process exit.
func (i *Instance) Close() error { func (i *Instance) Close() error {
if err := i.Terminal.Close(); err != nil { if err := i.Terminal.Close(); err != nil {
return err return err