From 07485bbd8ffe7167dbd94eca5fd2b423a3a606b8 Mon Sep 17 00:00:00 2001 From: Cheney Date: Tue, 16 Feb 2016 16:55:19 +0800 Subject: [PATCH] update history interfaces. and support DisableAutoSaveHistory --- .../readline-multiline/readline-multiline.go | 41 ++++++++++++++++++ history.go | 43 ++++++++++--------- operation.go | 41 +++++++++++------- readline.go | 8 +++- search.go | 4 +- 5 files changed, 97 insertions(+), 40 deletions(-) create mode 100644 example/readline-multiline/readline-multiline.go diff --git a/example/readline-multiline/readline-multiline.go b/example/readline-multiline/readline-multiline.go new file mode 100644 index 0000000..cb01d2b --- /dev/null +++ b/example/readline-multiline/readline-multiline.go @@ -0,0 +1,41 @@ +package main + +import ( + "strings" + + "github.com/chzyer/readline" +) + +func main() { + rl, err := readline.NewEx(&readline.Config{ + Prompt: "> ", + HistoryFile: "/tmp/readline-multiline", + DisableAutoSaveHistory: true, + }) + if err != nil { + panic(err) + } + defer rl.Close() + + var cmds []string + for { + line, err := rl.Readline() + if err != nil { + break + } + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + cmds = append(cmds, line) + if !strings.Contains(line, ";") { + rl.SetPrompt(">>> ") + continue + } + cmd := strings.Join(cmds, " ") + cmds = cmds[:0] + rl.SetPrompt("> ") + rl.SaveHistory(cmd) + println(cmd) + } +} diff --git a/history.go b/history.go index 3d1a672..f11c60f 100644 --- a/history.go +++ b/history.go @@ -40,7 +40,7 @@ func (o *opHistory) IsHistoryClosed() bool { return o.fd.Fd() == ^(uintptr(0)) } -func (o *opHistory) InitHistory() { +func (o *opHistory) Init() { if o.IsHistoryClosed() { o.initHistory() } @@ -66,24 +66,24 @@ func (o *opHistory) historyUpdatePath(path string) { if err != nil { break } - o.PushHistory([]rune(strings.TrimSpace(string(line)))) - o.CompactHistory() + o.Push([]rune(strings.TrimSpace(string(line)))) + o.Compact() } if total > o.cfg.HistoryLimit { - o.HistoryRewrite() + o.Rewrite() } o.historyVer++ - o.PushHistory(nil) + o.Push(nil) return } -func (o *opHistory) CompactHistory() { +func (o *opHistory) Compact() { for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { o.history.Remove(o.history.Front()) } } -func (o *opHistory) HistoryRewrite() { +func (o *opHistory) Rewrite() { if o.cfg.HistoryFile == "" { return } @@ -113,13 +113,13 @@ func (o *opHistory) HistoryRewrite() { o.fd = fd } -func (o *opHistory) CloseHistory() { +func (o *opHistory) Close() { if o.fd != nil { o.fd.Close() } } -func (o *opHistory) FindHistoryBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { for elem := o.current; elem != nil; elem = elem.Prev() { item := o.showItem(elem.Value) if isNewSearch { @@ -139,7 +139,7 @@ func (o *opHistory) FindHistoryBck(isNewSearch bool, rs []rune, start int) (int, return -1, nil } -func (o *opHistory) FindHistoryFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { for elem := o.current; elem != nil; elem = elem.Next() { item := o.showItem(elem.Value) if isNewSearch { @@ -175,7 +175,7 @@ func (o *opHistory) showItem(obj interface{}) []rune { return item.Source } -func (o *opHistory) PrevHistory() []rune { +func (o *opHistory) Prev() []rune { if o.current == nil { return nil } @@ -187,7 +187,7 @@ func (o *opHistory) PrevHistory() []rune { return o.showItem(current.Value) } -func (o *opHistory) NextHistory() ([]rune, bool) { +func (o *opHistory) Next() ([]rune, bool) { if o.current == nil { return nil, false } @@ -200,7 +200,8 @@ func (o *opHistory) NextHistory() ([]rune, bool) { return o.showItem(current.Value), true } -func (o *opHistory) NewHistory(current []rune) { +// save history +func (o *opHistory) New(current []rune) { // if just use last command without modify // just clean lastest history if back := o.history.Back(); back != nil { @@ -231,22 +232,22 @@ func (o *opHistory) NewHistory(current []rune) { current = use.Tmp } - o.UpdateHistory(current, true) + o.Update(current, true) // push a new one to commit current command o.historyVer++ - o.PushHistory(nil) + o.Push(nil) } -func (o *opHistory) RevertHistory() { +func (o *opHistory) Revert() { o.historyVer++ o.current = o.history.Back() } -func (o *opHistory) UpdateHistory(s []rune, commit bool) { +func (o *opHistory) Update(s []rune, commit bool) { if o.current == nil { - o.PushHistory(s) - o.CompactHistory() + o.Push(s) + o.Compact() return } r := o.current.Value.(*hisItem) @@ -261,10 +262,10 @@ func (o *opHistory) UpdateHistory(s []rune, commit bool) { r.Tmp = append(r.Tmp[:0], s...) } o.current.Value = r - o.CompactHistory() + o.Compact() } -func (o *opHistory) PushHistory(s []rune) { +func (o *opHistory) Push(s []rune) { newCopy := make([]rune, len(s)) copy(newCopy, s) elem := o.history.PushBack(&hisItem{Source: newCopy}) diff --git a/operation.go b/operation.go index e114e5f..503c624 100644 --- a/operation.go +++ b/operation.go @@ -20,7 +20,7 @@ type Operation struct { errchan chan error w io.Writer - *opHistory + history *opHistory *opSearch *opCompleter *opPassword @@ -95,7 +95,7 @@ func (o *Operation) ioloop() { o.buf.Refresh(nil) switch r { case CharEnter, CharCtrlJ: - o.UpdateHistory(o.buf.Runes(), false) + o.history.Update(o.buf.Runes(), false) fallthrough case CharInterrupt: o.t.KickRead() @@ -184,20 +184,24 @@ func (o *Operation) ioloop() { data = o.buf.Reset() } o.outchan <- data - o.NewHistory(data) + if !o.cfg.DisableAutoSaveHistory { + o.history.New(data) + } else { + isUpdateHistory = false + } case CharBackward: o.buf.MoveBackward() case CharForward: o.buf.MoveForward() case CharPrev: - buf := o.PrevHistory() + buf := o.history.Prev() if buf != nil { o.buf.Set(buf) } else { o.t.Bell() } case CharNext: - buf, ok := o.NextHistory() + buf, ok := o.history.Next() if ok { o.buf.Set(buf) } else { @@ -216,7 +220,7 @@ func (o *Operation) ioloop() { o.buf.WriteString(o.cfg.EOFPrompt + "\n") o.buf.Reset() isUpdateHistory = false - o.RevertHistory() + o.history.Revert() o.errchan <- io.EOF case CharInterrupt: if o.IsSearchMode() { @@ -235,7 +239,7 @@ func (o *Operation) ioloop() { o.buf.WriteString(o.cfg.InterruptPrompt + "\n") o.buf.Reset() isUpdateHistory = false - o.RevertHistory() + o.history.Revert() o.errchan <- ErrInterrupt default: if o.IsSearchMode() { @@ -270,7 +274,8 @@ func (o *Operation) ioloop() { } } if isUpdateHistory && !o.IsSearchMode() { - o.UpdateHistory(o.buf.Runes(), false) + // it will cause null history + o.history.Update(o.buf.Runes(), false) } } } @@ -353,15 +358,15 @@ func (o *Operation) Slice() ([]byte, error) { } func (o *Operation) Close() { - o.opHistory.CloseHistory() + o.history.Close() } func (o *Operation) SetHistoryPath(path string) { - if o.opHistory != nil { - o.opHistory.CloseHistory() + if o.history != nil { + o.history.Close() } o.cfg.HistoryFile = path - o.opHistory = newOpHistory(o.cfg) + o.history = newOpHistory(o.cfg) } func (o *Operation) IsNormalMode() bool { @@ -382,19 +387,23 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) { if cfg.opHistory == nil { op.SetHistoryPath(cfg.HistoryFile) - cfg.opHistory = op.opHistory - cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history) } - op.opHistory = cfg.opHistory + op.history = cfg.opHistory // SetHistoryPath will close opHistory which already exists // so if we use it next time, we need to reopen it by `InitHistory()` - op.opHistory.InitHistory() + op.history.Init() op.opSearch = cfg.opSearch return old, nil } +func (o *Operation) SaveHistory(content string) { + o.history.New([]rune(content)) +} + func (o *Operation) Refresh() { if o.t.IsReading() { o.buf.Refresh(nil) diff --git a/readline.go b/readline.go index 6ac68b9..4069b03 100644 --- a/readline.go +++ b/readline.go @@ -23,7 +23,8 @@ type Config struct { // readline will persist historys to file where HistoryFile specified HistoryFile string // specify the max length of historys, it's 500 by default, set it to -1 to disable history - HistoryLimit int + HistoryLimit int + DisableAutoSaveHistory bool // AutoCompleter will called once user press TAB AutoComplete AutoCompleter @@ -154,10 +155,15 @@ func (i *Instance) ReadPassword(prompt string) ([]byte, error) { return i.Operation.Password(prompt) } +// err is one of (nil, io.EOF, readline.ErrInterrupt) func (i *Instance) Readline() (string, error) { return i.Operation.String() } +func (i *Instance) SaveHistory(content string) { + i.Operation.SaveHistory(content) +} + // same as readline func (i *Instance) ReadSlice() ([]byte, error) { return i.Operation.Slice() diff --git a/search.go b/search.go index 4c09ba1..53d9e50 100644 --- a/search.go +++ b/search.go @@ -51,9 +51,9 @@ func (o *opSearch) SearchBackspace() { func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { if o.dir == S_DIR_BCK { - return o.history.FindHistoryBck(isNewSearch, o.data, o.buf.idx) + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) } - return o.history.FindHistoryFwd(isNewSearch, o.data, o.buf.idx) + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) } func (o *opSearch) search(isChange bool) bool {