-
Notifications
You must be signed in to change notification settings - Fork 0
/
cmd.go
154 lines (135 loc) · 3.34 KB
/
cmd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package store
import (
"encoding/binary"
"fmt"
"strings"
"sync"
"github.com/u-root/u-root/cmds/core/elvish/store/storedefs"
)
// The numbering is 1-based, not 0-based. Oh well.
type CmdHistory struct {
sync.Mutex
cmds map[int]string
cur int
max int
}
func (history *CmdHistory) Seek(i int) (int, error) {
history.Lock()
defer history.Unlock()
history.cur = history.cur + i
switch {
case history.cur < 1:
history.cur = 1
}
if history.cur > history.max {
history.max = history.cur
}
return history.cur, nil
}
// convenience functions
func (history *CmdHistory) Cur() (int, error) {
return history.Seek(0)
}
func (history *CmdHistory) Prev() (int, error) {
return history.Seek(-1)
}
func (history *CmdHistory) Next() (int, error) {
return history.Seek(1)
}
// AddCmd adds a new command to the command history.
func (history *CmdHistory) Add(cmd string) (int, error) {
history.Lock()
defer history.Unlock()
history.cur = len(history.cmds) + 1
history.cmds[history.cur] = cmd
if history.cur > history.max {
history.max = history.cur
}
return history.cur, nil
}
// Remove removes a command from command history referenced by
// sequence.
func (history *CmdHistory) Remove(seq int) error {
history.Lock()
defer history.Unlock()
delete(history.cmds, seq)
return nil
}
// Cmd queries the command history item with the specified sequence number.
func (history *CmdHistory) One(seq int) (string, error) {
history.Lock()
defer history.Unlock()
c, ok := history.cmds[seq]
if !ok {
return "", storedefs.ErrNoMatchingCmd
}
return c, nil
}
// IterateCmds iterates all the commands in the specified range, and calls the
// callback with the content of each command sequentially.
func (history *CmdHistory) Walk(from, upto int, f func(string) bool) error {
history.Lock()
defer history.Unlock()
for i := from; i < upto; i++ {
v, ok := history.cmds[i]
if !ok {
continue
}
if !f(v) {
return fmt.Errorf("%s fails", v)
}
}
return nil
}
// Cmds returns the contents of all commands within the specified range.
func (history *CmdHistory) List(from, upto int) ([]string, error) {
history.Lock()
defer history.Unlock()
var list []string
for i := from; i < upto; i++ {
v, ok := history.cmds[i]
if !ok {
continue
}
list = append(list, v)
}
return list, nil
}
// Search finds the first command after the given sequence number (inclusive)
// with the given prefix.
func (history *CmdHistory) Search(from int, prefix string) (int, string, error) {
l, _ := history.List(from, history.max)
for i, v := range l {
if strings.HasPrefix(v, prefix) {
return i + from, v, nil
}
}
return 0, "", storedefs.ErrNoMatchingCmd
}
// PrevCmd finds the last command before the given sequence number (exclusive)
// with the given prefix.
func (history *CmdHistory) RSearch(upto int, prefix string) (int, string, error) {
l, _ := history.List(1, upto)
for i := range l {
cmd := l[len(l)-i-1]
if strings.HasPrefix(cmd, prefix) {
return upto - i - 1, cmd, nil
}
}
return 0, "", storedefs.ErrNoMatchingCmd
}
func marshalSeq(seq uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(seq))
return b
}
func unmarshalSeq(key []byte) uint64 {
return binary.BigEndian.Uint64(key)
}
func NewCmdHistory(s ...string) storedefs.Store {
c := &CmdHistory{cmds: make(map[int]string)}
for _, v := range s {
c.Add(v)
}
return c
}