Path: blob/main/vendor/github.com/chzyer/readline/history.go
2875 views
package readline12import (3"bufio"4"container/list"5"fmt"6"os"7"strings"8"sync"9)1011type hisItem struct {12Source []rune13Version int6414Tmp []rune15}1617func (h *hisItem) Clean() {18h.Source = nil19h.Tmp = nil20}2122type opHistory struct {23cfg *Config24history *list.List25historyVer int6426current *list.Element27fd *os.File28fdLock sync.Mutex29enable bool30}3132func newOpHistory(cfg *Config) (o *opHistory) {33o = &opHistory{34cfg: cfg,35history: list.New(),36enable: true,37}38return o39}4041func (o *opHistory) Reset() {42o.history = list.New()43o.current = nil44}4546func (o *opHistory) IsHistoryClosed() bool {47o.fdLock.Lock()48defer o.fdLock.Unlock()49return o.fd.Fd() == ^(uintptr(0))50}5152func (o *opHistory) Init() {53if o.IsHistoryClosed() {54o.initHistory()55}56}5758func (o *opHistory) initHistory() {59if o.cfg.HistoryFile != "" {60o.historyUpdatePath(o.cfg.HistoryFile)61}62}6364// only called by newOpHistory65func (o *opHistory) historyUpdatePath(path string) {66o.fdLock.Lock()67defer o.fdLock.Unlock()68f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)69if err != nil {70return71}72o.fd = f73r := bufio.NewReader(o.fd)74total := 075for ; ; total++ {76line, err := r.ReadString('\n')77if err != nil {78break79}80// ignore the empty line81line = strings.TrimSpace(line)82if len(line) == 0 {83continue84}85o.Push([]rune(line))86o.Compact()87}88if total > o.cfg.HistoryLimit {89o.rewriteLocked()90}91o.historyVer++92o.Push(nil)93return94}9596func (o *opHistory) Compact() {97for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {98o.history.Remove(o.history.Front())99}100}101102func (o *opHistory) Rewrite() {103o.fdLock.Lock()104defer o.fdLock.Unlock()105o.rewriteLocked()106}107108func (o *opHistory) rewriteLocked() {109if o.cfg.HistoryFile == "" {110return111}112113tmpFile := o.cfg.HistoryFile + ".tmp"114fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)115if err != nil {116return117}118119buf := bufio.NewWriter(fd)120for elem := o.history.Front(); elem != nil; elem = elem.Next() {121buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")122}123buf.Flush()124125// replace history file126if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {127fd.Close()128return129}130131if o.fd != nil {132o.fd.Close()133}134// fd is write only, just satisfy what we need.135o.fd = fd136}137138func (o *opHistory) Close() {139o.fdLock.Lock()140defer o.fdLock.Unlock()141if o.fd != nil {142o.fd.Close()143}144}145146func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {147for elem := o.current; elem != nil; elem = elem.Prev() {148item := o.showItem(elem.Value)149if isNewSearch {150start += len(rs)151}152if elem == o.current {153if len(item) >= start {154item = item[:start]155}156}157idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)158if idx < 0 {159continue160}161return idx, elem162}163return -1, nil164}165166func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {167for elem := o.current; elem != nil; elem = elem.Next() {168item := o.showItem(elem.Value)169if isNewSearch {170start -= len(rs)171if start < 0 {172start = 0173}174}175if elem == o.current {176if len(item)-1 >= start {177item = item[start:]178} else {179continue180}181}182idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)183if idx < 0 {184continue185}186if elem == o.current {187idx += start188}189return idx, elem190}191return -1, nil192}193194func (o *opHistory) showItem(obj interface{}) []rune {195item := obj.(*hisItem)196if item.Version == o.historyVer {197return item.Tmp198}199return item.Source200}201202func (o *opHistory) Prev() []rune {203if o.current == nil {204return nil205}206current := o.current.Prev()207if current == nil {208return nil209}210o.current = current211return runes.Copy(o.showItem(current.Value))212}213214func (o *opHistory) Next() ([]rune, bool) {215if o.current == nil {216return nil, false217}218current := o.current.Next()219if current == nil {220return nil, false221}222223o.current = current224return runes.Copy(o.showItem(current.Value)), true225}226227// Disable the current history228func (o *opHistory) Disable() {229o.enable = false230}231232// Enable the current history233func (o *opHistory) Enable() {234o.enable = true235}236237func (o *opHistory) debug() {238Debug("-------")239for item := o.history.Front(); item != nil; item = item.Next() {240Debug(fmt.Sprintf("%+v", item.Value))241}242}243244// save history245func (o *opHistory) New(current []rune) (err error) {246247// history deactivated248if !o.enable {249return nil250}251252current = runes.Copy(current)253254// if just use last command without modify255// just clean lastest history256if back := o.history.Back(); back != nil {257prev := back.Prev()258if prev != nil {259if runes.Equal(current, prev.Value.(*hisItem).Source) {260o.current = o.history.Back()261o.current.Value.(*hisItem).Clean()262o.historyVer++263return nil264}265}266}267268if len(current) == 0 {269o.current = o.history.Back()270if o.current != nil {271o.current.Value.(*hisItem).Clean()272o.historyVer++273return nil274}275}276277if o.current != o.history.Back() {278// move history item to current command279currentItem := o.current.Value.(*hisItem)280// set current to last item281o.current = o.history.Back()282283current = runes.Copy(currentItem.Tmp)284}285286// err only can be a IO error, just report287err = o.Update(current, true)288289// push a new one to commit current command290o.historyVer++291o.Push(nil)292return293}294295func (o *opHistory) Revert() {296o.historyVer++297o.current = o.history.Back()298}299300func (o *opHistory) Update(s []rune, commit bool) (err error) {301o.fdLock.Lock()302defer o.fdLock.Unlock()303s = runes.Copy(s)304if o.current == nil {305o.Push(s)306o.Compact()307return308}309r := o.current.Value.(*hisItem)310r.Version = o.historyVer311if commit {312r.Source = s313if o.fd != nil {314// just report the error315_, err = o.fd.Write([]byte(string(r.Source) + "\n"))316}317} else {318r.Tmp = append(r.Tmp[:0], s...)319}320o.current.Value = r321o.Compact()322return323}324325func (o *opHistory) Push(s []rune) {326s = runes.Copy(s)327elem := o.history.PushBack(&hisItem{Source: s})328o.current = elem329}330331332