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 }