331 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
| package readline
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"container/list"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| type hisItem struct {
 | |
| 	Source  []rune
 | |
| 	Version int64
 | |
| 	Tmp     []rune
 | |
| }
 | |
| 
 | |
| func (h *hisItem) Clean() {
 | |
| 	h.Source = nil
 | |
| 	h.Tmp = nil
 | |
| }
 | |
| 
 | |
| type opHistory struct {
 | |
| 	cfg        *Config
 | |
| 	history    *list.List
 | |
| 	historyVer int64
 | |
| 	current    *list.Element
 | |
| 	fd         *os.File
 | |
| 	fdLock     sync.Mutex
 | |
| 	enable     bool
 | |
| }
 | |
| 
 | |
| func newOpHistory(cfg *Config) (o *opHistory) {
 | |
| 	o = &opHistory{
 | |
| 		cfg:     cfg,
 | |
| 		history: list.New(),
 | |
| 		enable:  true,
 | |
| 	}
 | |
| 	return o
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Reset() {
 | |
| 	o.history = list.New()
 | |
| 	o.current = nil
 | |
| }
 | |
| 
 | |
| func (o *opHistory) IsHistoryClosed() bool {
 | |
| 	o.fdLock.Lock()
 | |
| 	defer o.fdLock.Unlock()
 | |
| 	return o.fd.Fd() == ^(uintptr(0))
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Init() {
 | |
| 	if o.IsHistoryClosed() {
 | |
| 		o.initHistory()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *opHistory) initHistory() {
 | |
| 	if o.cfg.HistoryFile != "" {
 | |
| 		o.historyUpdatePath(o.cfg.HistoryFile)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // only called by newOpHistory
 | |
| func (o *opHistory) historyUpdatePath(path string) {
 | |
| 	o.fdLock.Lock()
 | |
| 	defer o.fdLock.Unlock()
 | |
| 	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)
 | |
| 	total := 0
 | |
| 	for ; ; total++ {
 | |
| 		line, err := r.ReadString('\n')
 | |
| 		if err != nil {
 | |
| 			break
 | |
| 		}
 | |
| 		// ignore the empty line
 | |
| 		line = strings.TrimSpace(line)
 | |
| 		if len(line) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		o.Push([]rune(line))
 | |
| 		o.Compact()
 | |
| 	}
 | |
| 	if total > o.cfg.HistoryLimit {
 | |
| 		o.rewriteLocked()
 | |
| 	}
 | |
| 	o.historyVer++
 | |
| 	o.Push(nil)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Compact() {
 | |
| 	for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
 | |
| 		o.history.Remove(o.history.Front())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Rewrite() {
 | |
| 	o.fdLock.Lock()
 | |
| 	defer o.fdLock.Unlock()
 | |
| 	o.rewriteLocked()
 | |
| }
 | |
| 
 | |
| func (o *opHistory) rewriteLocked() {
 | |
| 	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
 | |
| 	}
 | |
| 
 | |
| 	buf := bufio.NewWriter(fd)
 | |
| 	for elem := o.history.Front(); elem != nil; elem = elem.Next() {
 | |
| 		buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
 | |
| 	}
 | |
| 	buf.Flush()
 | |
| 
 | |
| 	// replace history file
 | |
| 	if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {
 | |
| 		fd.Close()
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if o.fd != nil {
 | |
| 		o.fd.Close()
 | |
| 	}
 | |
| 	// fd is write only, just satisfy what we need.
 | |
| 	o.fd = fd
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Close() {
 | |
| 	o.fdLock.Lock()
 | |
| 	defer o.fdLock.Unlock()
 | |
| 	if o.fd != nil {
 | |
| 		o.fd.Close()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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 {
 | |
| 			start += len(rs)
 | |
| 		}
 | |
| 		if elem == o.current {
 | |
| 			if len(item) >= start {
 | |
| 				item = item[:start]
 | |
| 			}
 | |
| 		}
 | |
| 		idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)
 | |
| 		if idx < 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		return idx, elem
 | |
| 	}
 | |
| 	return -1, nil
 | |
| }
 | |
| 
 | |
| 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 {
 | |
| 			start -= len(rs)
 | |
| 			if start < 0 {
 | |
| 				start = 0
 | |
| 			}
 | |
| 		}
 | |
| 		if elem == o.current {
 | |
| 			if len(item)-1 >= start {
 | |
| 				item = item[start:]
 | |
| 			} else {
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)
 | |
| 		if idx < 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if elem == o.current {
 | |
| 			idx += start
 | |
| 		}
 | |
| 		return idx, elem
 | |
| 	}
 | |
| 	return -1, nil
 | |
| }
 | |
| 
 | |
| func (o *opHistory) showItem(obj interface{}) []rune {
 | |
| 	item := obj.(*hisItem)
 | |
| 	if item.Version == o.historyVer {
 | |
| 		return item.Tmp
 | |
| 	}
 | |
| 	return item.Source
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Prev() []rune {
 | |
| 	if o.current == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	current := o.current.Prev()
 | |
| 	if current == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	o.current = current
 | |
| 	return runes.Copy(o.showItem(current.Value))
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Next() ([]rune, bool) {
 | |
| 	if o.current == nil {
 | |
| 		return nil, false
 | |
| 	}
 | |
| 	current := o.current.Next()
 | |
| 	if current == nil {
 | |
| 		return nil, false
 | |
| 	}
 | |
| 
 | |
| 	o.current = current
 | |
| 	return runes.Copy(o.showItem(current.Value)), true
 | |
| }
 | |
| 
 | |
| // Disable the current history
 | |
| func (o *opHistory) Disable() {
 | |
| 	o.enable = false
 | |
| }
 | |
| 
 | |
| // Enable the current history
 | |
| func (o *opHistory) Enable() {
 | |
| 	o.enable = true
 | |
| }
 | |
| 
 | |
| func (o *opHistory) debug() {
 | |
| 	Debug("-------")
 | |
| 	for item := o.history.Front(); item != nil; item = item.Next() {
 | |
| 		Debug(fmt.Sprintf("%+v", item.Value))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // save history
 | |
| func (o *opHistory) New(current []rune) (err error) {
 | |
| 
 | |
| 	// history deactivated
 | |
| 	if !o.enable {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	current = runes.Copy(current)
 | |
| 
 | |
| 	// if just use last command without modify
 | |
| 	// just clean lastest history
 | |
| 	if back := o.history.Back(); back != nil {
 | |
| 		prev := back.Prev()
 | |
| 		if prev != nil {
 | |
| 			if runes.Equal(current, prev.Value.(*hisItem).Source) {
 | |
| 				o.current = o.history.Back()
 | |
| 				o.current.Value.(*hisItem).Clean()
 | |
| 				o.historyVer++
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(current) == 0 {
 | |
| 		o.current = o.history.Back()
 | |
| 		if o.current != nil {
 | |
| 			o.current.Value.(*hisItem).Clean()
 | |
| 			o.historyVer++
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if o.current != o.history.Back() {
 | |
| 		// move history item to current command
 | |
| 		currentItem := o.current.Value.(*hisItem)
 | |
| 		// set current to last item
 | |
| 		o.current = o.history.Back()
 | |
| 
 | |
| 		current = runes.Copy(currentItem.Tmp)
 | |
| 	}
 | |
| 
 | |
| 	// err only can be a IO error, just report
 | |
| 	err = o.Update(current, true)
 | |
| 
 | |
| 	// push a new one to commit current command
 | |
| 	o.historyVer++
 | |
| 	o.Push(nil)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Revert() {
 | |
| 	o.historyVer++
 | |
| 	o.current = o.history.Back()
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Update(s []rune, commit bool) (err error) {
 | |
| 	o.fdLock.Lock()
 | |
| 	defer o.fdLock.Unlock()
 | |
| 	s = runes.Copy(s)
 | |
| 	if o.current == nil {
 | |
| 		o.Push(s)
 | |
| 		o.Compact()
 | |
| 		return
 | |
| 	}
 | |
| 	r := o.current.Value.(*hisItem)
 | |
| 	r.Version = o.historyVer
 | |
| 	if commit {
 | |
| 		r.Source = s
 | |
| 		if o.fd != nil {
 | |
| 			// just report the error
 | |
| 			_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
 | |
| 		}
 | |
| 	} else {
 | |
| 		r.Tmp = append(r.Tmp[:0], s...)
 | |
| 	}
 | |
| 	o.current.Value = r
 | |
| 	o.Compact()
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (o *opHistory) Push(s []rune) {
 | |
| 	s = runes.Copy(s)
 | |
| 	elem := o.history.PushBack(&hisItem{Source: s})
 | |
| 	o.current = elem
 | |
| }
 |