From bfa8c1dfdb882bc24b2d807fc6bf6f6e7eb7332e Mon Sep 17 00:00:00 2001 From: Cheney Date: Sun, 4 Oct 2015 15:23:00 +0800 Subject: [PATCH] add history limitation --- history.go | 58 ++++++++++++++++++++++++++++++++++++++++++++-------- operation.go | 20 ++++++++++++------ readline.go | 35 ++++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 20 deletions(-) diff --git a/history.go b/history.go index 5ca74c9..8923b6f 100644 --- a/history.go +++ b/history.go @@ -19,40 +19,81 @@ func (h *hisItem) Clean() { } type opHistory struct { - path string + cfg *Config history *list.List historyVer int64 current *list.Element fd *os.File } -func newOpHistory(path string) (o *opHistory) { +func newOpHistory(cfg *Config) (o *opHistory) { o = &opHistory{ - path: path, + cfg: cfg, history: list.New(), } - if o.path == "" { - return + if o.cfg.HistoryFile != "" { + 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 { return } o.fd = f r := bufio.NewReader(o.fd) - for { + total := 0 + for ; ; total++ { line, err := r.ReadSlice('\n') if err != nil { break } o.PushHistory([]rune(strings.TrimSpace(string(line)))) + o.CompactHistory() + } + if total > o.cfg.HistoryLimit { + o.HistoryRewrite() } o.historyVer++ o.PushHistory(nil) 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 { o.fd.Close() } @@ -197,7 +238,6 @@ func (o *opHistory) UpdateHistory(s []rune, commit bool) { } func (o *opHistory) PushHistory(s []rune) { - // copy newCopy := make([]rune, len(s)) copy(newCopy, s) elem := o.history.PushBack(&hisItem{Source: newCopy}) diff --git a/operation.go b/operation.go index 180800d..b3a5c5d 100644 --- a/operation.go +++ b/operation.go @@ -51,12 +51,12 @@ func (w *wrapWriter) Write(b []byte) (int, error) { func NewOperation(t *Terminal, cfg *Config) *Operation { op := &Operation{ - cfg: cfg, - t: t, - buf: NewRuneBuffer(t, cfg.Prompt), - outchan: make(chan []rune), - opHistory: newOpHistory(cfg.HistoryFile), + cfg: cfg, + t: t, + buf: NewRuneBuffer(t, cfg.Prompt), + outchan: make(chan []rune), } + op.SetHistoryPath(cfg.HistoryFile) op.opVim = newVimMode(op) op.w = op.buf.w op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory) @@ -286,5 +286,13 @@ func (o *Operation) Slice() ([]byte, error) { } 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) } diff --git a/readline.go b/readline.go index 8269f22..cd075f4 100644 --- a/readline.go +++ b/readline.go @@ -3,17 +3,28 @@ package readline import "io" type Instance struct { + Config *Config Terminal *Terminal Operation *Operation } type Config struct { - Prompt string - HistoryFile string + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default + HistoryLimit int + + // AutoCompleter will called once user press TAB AutoComplete AutoCompleter - VimMode bool - Stdout io.Writer - Stderr io.Writer + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + Stdout io.Writer + Stderr io.Writer inited bool } @@ -29,6 +40,9 @@ func (c *Config) Init() error { if c.Stderr == nil { c.Stderr = Stderr } + if c.HistoryLimit < 0 { + c.HistoryLimit = 500 + } return nil } @@ -39,6 +53,7 @@ func NewEx(cfg *Config) (*Instance, error) { } rl := t.Readline() return &Instance{ + Config: cfg, Terminal: t, Operation: rl, }, nil @@ -52,14 +67,22 @@ func (i *Instance) SetPrompt(s string) { 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 { return i.Operation.Stdout() } +// readline will refresh automatic when write through Stdout() func (i *Instance) Stderr() io.Writer { return i.Operation.Stderr() } +// switch VimMode in runtime func (i *Instance) SetVimMode(on bool) { i.Operation.SetVimMode(on) } @@ -76,10 +99,12 @@ func (i *Instance) Readline() (string, error) { return i.Operation.String() } +// same as readline func (i *Instance) ReadSlice() ([]byte, error) { return i.Operation.Slice() } +// we must make sure that call Close() before process exit. func (i *Instance) Close() error { if err := i.Terminal.Close(); err != nil { return err