gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/elvish/edit/history/walker.go (about)

     1  package history
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	"github.com/u-root/u-root/cmds/core/elvish/store/storedefs"
     8  )
     9  
    10  var ErrEndOfHistory = errors.New("end of history")
    11  
    12  // Walker is used for walking through history entries with a given (possibly
    13  // empty) prefix, skipping duplicates entries.
    14  type Walker struct {
    15  	store       storedefs.Store
    16  	storeUpper  int
    17  	sessionCmds []string
    18  	sessionSeqs []int
    19  	prefix      string
    20  
    21  	// The next element to fetch from the session history. If equal to -1, the
    22  	// next element comes from the storage backend.
    23  	sessionIdx int
    24  	// Index of the next element in the stack that Prev will return on next
    25  	// call. If equal to len(stack), the next element needs to be fetched,
    26  	// either from the session history or the storage backend.
    27  	top     int
    28  	stack   []string
    29  	seq     []int
    30  	inStack map[string]bool
    31  }
    32  
    33  func NewWalker(store storedefs.Store, upper int, cmds []string, seqs []int, prefix string) *Walker {
    34  	return &Walker{store, upper, cmds, seqs, prefix,
    35  		len(cmds) - 1, 0, nil, nil, map[string]bool{}}
    36  }
    37  
    38  // Prefix returns the prefix of the commands that the walker walks through.
    39  func (w *Walker) Prefix() string {
    40  	return w.prefix
    41  }
    42  
    43  // CurrentSeq returns the sequence number of the current entry.
    44  func (w *Walker) CurrentSeq() int {
    45  	if len(w.seq) > 0 && w.top <= len(w.seq) && w.top > 0 {
    46  		return w.seq[w.top-1]
    47  	}
    48  	return -1
    49  }
    50  
    51  // CurrentSeq returns the content of the current entry.
    52  func (w *Walker) CurrentCmd() string {
    53  	if len(w.stack) > 0 && w.top <= len(w.stack) && w.top > 0 {
    54  		return w.stack[w.top-1]
    55  	}
    56  	return ""
    57  }
    58  
    59  // Prev walks to the previous matching history entry, skipping all duplicates.
    60  func (w *Walker) Prev() (int, string, error) {
    61  	// Entry comes from the stack.
    62  	if w.top < len(w.stack) {
    63  		i := w.top
    64  		w.top++
    65  		return w.seq[i], w.stack[i], nil
    66  	}
    67  
    68  	// Find the entry in the session part.
    69  	for i := w.sessionIdx; i >= 0; i-- {
    70  		seq := w.sessionSeqs[i]
    71  		cmd := w.sessionCmds[i]
    72  		if strings.HasPrefix(cmd, w.prefix) && !w.inStack[cmd] {
    73  			w.push(cmd, seq)
    74  			w.sessionIdx = i - 1
    75  			return seq, cmd, nil
    76  		}
    77  	}
    78  	// Not found in the session part.
    79  	w.sessionIdx = -1
    80  
    81  	seq := w.storeUpper
    82  	if len(w.seq) > 0 && seq > w.seq[len(w.seq)-1] {
    83  		seq = w.seq[len(w.seq)-1]
    84  	}
    85  	for {
    86  		var (
    87  			cmd string
    88  			err error
    89  		)
    90  		seq, cmd, err = w.store.RSearch(seq, w.prefix)
    91  		if err != nil {
    92  			if err.Error() == storedefs.ErrNoMatchingCmd.Error() {
    93  				err = ErrEndOfHistory
    94  			}
    95  			return -1, "", err
    96  		}
    97  		if !w.inStack[cmd] {
    98  			w.push(cmd, seq)
    99  			return seq, cmd, nil
   100  		}
   101  	}
   102  }
   103  
   104  func (w *Walker) push(cmd string, seq int) {
   105  	w.inStack[cmd] = true
   106  	w.stack = append(w.stack, cmd)
   107  	w.seq = append(w.seq, seq)
   108  	w.top++
   109  }
   110  
   111  // Next reverses Prev.
   112  func (w *Walker) Next() (int, string, error) {
   113  	if w.top <= 0 {
   114  		return -1, "", ErrEndOfHistory
   115  	}
   116  	w.top--
   117  	if w.top == 0 {
   118  		return -1, "", ErrEndOfHistory
   119  	}
   120  	return w.seq[w.top-1], w.stack[w.top-1], nil
   121  }