matcher.go RawHistoryFolder
1package vimkeys
2
3import "strconv"
4
5// ResultKind classifies matcher feed outcomes.
6type ResultKind int
7
8const (
9 ResultPending ResultKind = iota
10 ResultComplete
11 ResultInvalid
12)
13
14// Result is the outcome of feeding a key into the matcher.
15type Result struct {
16 Kind ResultKind
17 Count int
18 Action string
19 Pending string
20}
21
22// Matcher incrementally matches key sequences against a trie.
23type Matcher struct {
24 trie *Trie
25 node *trieNode
26 pending Sequence
27 count int
28 inCount bool
29 ambiguous bool
30}
31
32// NewMatcher creates a matcher backed by trie.
33func NewMatcher(trie *Trie) *Matcher {
34 return &Matcher{trie: trie}
35}
36
37// Reset clears matcher state.
38func (m *Matcher) Reset() {
39 if m == nil {
40 return
41 }
42 m.node = nil
43 m.pending = nil
44 m.count = 0
45 m.inCount = false
46 m.ambiguous = false
47}
48
49// Pending returns the count prefix and keys matched so far.
50func (m *Matcher) Pending() string {
51 if !m.hasTrie() {
52 return ""
53 }
54 return formatPending(m.count, m.pending)
55}
56
57// Ambiguous reports whether the current prefix is both exact and extendable.
58func (m *Matcher) Ambiguous() bool {
59 return m.hasTrie() && m.ambiguous
60}
61
62// Feed consumes one key and returns the current match state.
63func (m *Matcher) Feed(key Key) Result {
64 if !m.hasTrie() {
65 m.Reset()
66 return Result{Kind: ResultInvalid}
67 }
68
69 if m.node == nil && len(m.pending) == 0 && m.count == 0 && !m.inCount {
70 m.beginSequence()
71 }
72
73 if m.inCount {
74 return m.feedCountDigit(key)
75 }
76
77 if len(m.pending) == 0 && isCountStartDigit(key) {
78 m.inCount = true
79 return m.feedCountDigit(key)
80 }
81
82 return m.feedSequenceKey(key)
83}
84
85func (m *Matcher) hasTrie() bool {
86 return m != nil && m.trie != nil && m.trie.root != nil
87}
88
89func (m *Matcher) beginSequence() {
90 if !m.hasTrie() {
91 m.Reset()
92 return
93 }
94 m.node = m.trie.root
95 m.pending = nil
96 m.count = 0
97 m.inCount = false
98 m.ambiguous = false
99}
100
101func (m *Matcher) feedCountDigit(key Key) Result {
102 if !isCountDigit(key) {
103 m.inCount = false
104 m.node = m.trie.root
105 return m.feedSequenceKey(key)
106 }
107
108 digit := int(key.Sym[0] - '0')
109 next := m.count*10 + digit
110 if next > maxCount {
111 next = maxCount
112 }
113 m.count = next
114 return Result{Kind: ResultPending, Count: normalizedCount(m.count), Pending: m.Pending()}
115}
116
117func (m *Matcher) feedSequenceKey(key Key) Result {
118 if m.node == nil {
119 result := Result{Kind: ResultInvalid, Count: normalizedCount(m.count)}
120 m.Reset()
121 return result
122 }
123
124 child, ok := m.node.children[key]
125 if !ok {
126 result := Result{Kind: ResultInvalid, Count: normalizedCount(m.count)}
127 m.Reset()
128 return result
129 }
130
131 m.pending = append(m.pending, key)
132 m.node = child
133
134 exact := m.node.hasAction
135 children := len(m.node.children) > 0
136 m.ambiguous = exact && children
137
138 switch {
139 case m.ambiguous:
140 return Result{Kind: ResultPending, Count: normalizedCount(m.count), Pending: m.Pending()}
141 case exact:
142 result := Result{
143 Kind: ResultComplete,
144 Count: normalizedCount(m.count),
145 Action: m.node.action,
146 }
147 m.Reset()
148 return result
149 case children:
150 return Result{Kind: ResultPending, Count: normalizedCount(m.count), Pending: m.Pending()}
151 default:
152 result := Result{Kind: ResultInvalid, Count: normalizedCount(m.count)}
153 m.Reset()
154 return result
155 }
156}
157
158// ResolveAmbiguity completes the current short exact binding.
159func (m *Matcher) ResolveAmbiguity() (Result, bool) {
160 if !m.hasTrie() || !m.ambiguous || m.node == nil || !m.node.hasAction {
161 return Result{}, false
162 }
163
164 result := Result{
165 Kind: ResultComplete,
166 Count: normalizedCount(m.count),
167 Action: m.node.action,
168 }
169 m.Reset()
170 return result, true
171}
172
173func isCountStartDigit(key Key) bool {
174 return key.Mods == 0 && len(key.Sym) == 1 && key.Sym[0] >= '1' && key.Sym[0] <= '9'
175}
176
177func isCountDigit(key Key) bool {
178 return key.Mods == 0 && len(key.Sym) == 1 && key.Sym[0] >= '0' && key.Sym[0] <= '9'
179}
180
181func normalizedCount(count int) int {
182 if count <= 0 {
183 return 0
184 }
185 return count
186}
187
188func formatPending(count int, pending Sequence) string {
189 var out string
190 if count > 0 {
191 out = strconv.Itoa(count)
192 }
193 if len(pending) > 0 {
194 out += pending.String()
195 }
196 return out
197}