matcher_test.go RawHistoryFolder

1package vimkeys
2
3import (
4	"testing"
5)
6
7func testTrie(t *testing.T) *Trie {
8	t.Helper()
9	trie := NewTrie()
10	bindings := []struct {
11		seq    Sequence
12		action string
13	}{
14		{seq: Sequence{{Sym: "]"}, {Sym: "]"}}, action: "double-bracket"},
15		{seq: Sequence{{Sym: "]"}, {Sym: "c"}}, action: "bracket-c"},
16		{seq: Sequence{{Sym: "g"}, {Sym: "o", Mods: ModShift}}, action: "gO"},
17		{seq: Sequence{{Sym: "y"}, {Sym: "a"}, {Sym: "h"}}, action: "yah"},
18		{seq: Sequence{{Sym: "]"}}, action: "single-bracket"},
19		{seq: Sequence{{Sym: "j"}}, action: "down"},
20		{seq: Sequence{{Sym: "0"}}, action: "zero"},
21	}
22	for _, binding := range bindings {
23		if err := trie.Insert(binding.seq, binding.action); err != nil {
24			t.Fatalf("Insert(%v) error = %v", binding.seq, err)
25		}
26	}
27	return trie
28}
29
30func TestMatcher_FeedPrefixesPending(t *testing.T) {
31	m := NewMatcher(testTrie(t))
32
33	cases := []struct {
34		name    string
35		keys    []Key
36		want    ResultKind
37		pending string
38	}{
39		{
40			name:    "]] first bracket",
41			keys:    []Key{{Sym: "]"}},
42			want:    ResultPending,
43			pending: "]",
44		},
45		{
46			name:    "gO prefix g",
47			keys:    []Key{{Sym: "g"}},
48			want:    ResultPending,
49			pending: "g",
50		},
51		{
52			name:    "yah prefix ya",
53			keys:    []Key{{Sym: "y"}, {Sym: "a"}},
54			want:    ResultPending,
55			pending: "ya",
56		},
57	}
58	for _, tt := range cases {
59		t.Run(tt.name, func(t *testing.T) {
60			m.Reset()
61			var result Result
62			for _, key := range tt.keys {
63				result = m.Feed(key)
64			}
65			if result.Kind != tt.want {
66				t.Fatalf("Feed kind = %v, want %v", result.Kind, tt.want)
67			}
68			if result.Pending != tt.pending {
69				t.Fatalf("Pending = %q, want %q", result.Pending, tt.pending)
70			}
71			if m.Pending() != tt.pending {
72				t.Fatalf("Matcher.Pending() = %q, want %q", m.Pending(), tt.pending)
73			}
74			if result.Count != 0 {
75				t.Fatalf("Count = %d, want 0 (absent)", result.Count)
76			}
77		})
78	}
79}
80
81func TestMatcher_FeedComplete(t *testing.T) {
82	m := NewMatcher(testTrie(t))
83
84	cases := []struct {
85		name   string
86		keys   []Key
87		action string
88	}{
89		{name: "]]", keys: []Key{{Sym: "]"}, {Sym: "]"}}, action: "double-bracket"},
90		{name: "gO", keys: []Key{{Sym: "g"}, {Sym: "o", Mods: ModShift}}, action: "gO"},
91		{name: "yah", keys: []Key{{Sym: "y"}, {Sym: "a"}, {Sym: "h"}}, action: "yah"},
92	}
93	for _, tt := range cases {
94		t.Run(tt.name, func(t *testing.T) {
95			m.Reset()
96			var result Result
97			for _, key := range tt.keys {
98				result = m.Feed(key)
99			}
100			if result.Kind != ResultComplete {
101				t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultComplete)
102			}
103			if result.Action != tt.action {
104				t.Fatalf("Action = %q, want %q", result.Action, tt.action)
105			}
106			if result.Count != 0 {
107				t.Fatalf("Count = %d, want 0", result.Count)
108			}
109		})
110	}
111}
112
113func TestMatcher_Counts(t *testing.T) {
114	m := NewMatcher(testTrie(t))
115
116	cases := []struct {
117		name   string
118		keys   []Key
119		count  int
120		action string
121	}{
122		{
123			name:   "2]]",
124			keys:   []Key{{Sym: "2"}, {Sym: "]"}, {Sym: "]"}},
125			count:  2,
126			action: "double-bracket",
127		},
128		{
129			name:   "3j",
130			keys:   []Key{{Sym: "3"}, {Sym: "j"}},
131			count:  3,
132			action: "down",
133		},
134		{
135			name:   "10j",
136			keys:   []Key{{Sym: "1"}, {Sym: "0"}, {Sym: "j"}},
137			count:  10,
138			action: "down",
139		},
140		{
141			name:   "0 binding",
142			keys:   []Key{{Sym: "0"}},
143			count:  0,
144			action: "zero",
145		},
146	}
147	for _, tt := range cases {
148		t.Run(tt.name, func(t *testing.T) {
149			m.Reset()
150			var result Result
151			for _, key := range tt.keys {
152				result = m.Feed(key)
153			}
154			if result.Kind != ResultComplete {
155				t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultComplete)
156			}
157			if result.Count != tt.count {
158				t.Fatalf("Count = %d, want %d", result.Count, tt.count)
159			}
160			if result.Action != tt.action {
161				t.Fatalf("Action = %q, want %q", result.Action, tt.action)
162			}
163		})
164	}
165}
166
167func TestMatcher_ZeroThenCount(t *testing.T) {
168	m := NewMatcher(testTrie(t))
169
170	zero := m.Feed(Key{Sym: "0"})
171	if zero.Kind != ResultComplete || zero.Action != "zero" || zero.Count != 0 {
172		t.Fatalf("first zero result = %#v, want complete zero binding", zero)
173	}
174
175	count := m.Feed(Key{Sym: "3"})
176	if count.Kind != ResultPending || count.Pending != "3" || count.Count != 3 {
177		t.Fatalf("count prefix result = %#v, want pending count 3", count)
178	}
179
180	complete := m.Feed(Key{Sym: "j"})
181	if complete.Kind != ResultComplete || complete.Action != "down" || complete.Count != 3 {
182		t.Fatalf("counted action result = %#v, want complete down count 3", complete)
183	}
184}
185
186func TestMatcher_CountPendingString(t *testing.T) {
187	m := NewMatcher(testTrie(t))
188	m.Reset()
189	result := m.Feed(Key{Sym: "3"})
190	if result.Kind != ResultPending {
191		t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultPending)
192	}
193	if result.Pending != "3" {
194		t.Fatalf("Pending = %q, want %q", result.Pending, "3")
195	}
196	if m.Pending() != "3" {
197		t.Fatalf("Matcher.Pending() = %q, want %q", m.Pending(), "3")
198	}
199}
200
201func TestMatcher_CountCap9999(t *testing.T) {
202	m := NewMatcher(testTrie(t))
203	digits := make([]Key, 0, 5)
204	for _, ch := range "10000" {
205		digits = append(digits, Key{Sym: string(ch)})
206	}
207	digits = append(digits, Key{Sym: "j"})
208
209	m.Reset()
210	var result Result
211	for _, key := range digits {
212		result = m.Feed(key)
213	}
214	if result.Kind != ResultComplete {
215		t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultComplete)
216	}
217	if result.Count != 9999 {
218		t.Fatalf("Count = %d, want 9999", result.Count)
219	}
220}
221
222func TestMatcher_AutomaticReset(t *testing.T) {
223	m := NewMatcher(testTrie(t))
224
225	m.Reset()
226	complete := m.Feed(Key{Sym: "j"})
227	if complete.Kind != ResultComplete {
228		t.Fatalf("first Feed kind = %v, want %v", complete.Kind, ResultComplete)
229	}
230
231	next := m.Feed(Key{Sym: "g"})
232	if next.Kind != ResultPending {
233		t.Fatalf("after complete Feed kind = %v, want %v", next.Kind, ResultPending)
234	}
235	if m.Pending() != "g" {
236		t.Fatalf("Pending after reset = %q, want g", m.Pending())
237	}
238
239	m.Reset()
240	invalid := m.Feed(Key{Sym: "x"})
241	if invalid.Kind != ResultInvalid {
242		t.Fatalf("invalid Feed kind = %v, want %v", invalid.Kind, ResultInvalid)
243	}
244
245	afterInvalid := m.Feed(Key{Sym: "j"})
246	if afterInvalid.Kind != ResultComplete {
247		t.Fatalf("after invalid Feed kind = %v, want %v", afterInvalid.Kind, ResultComplete)
248	}
249}
250
251func TestMatcher_AmbiguityClearsOnTransition(t *testing.T) {
252	trie := NewTrie()
253	for _, binding := range []struct {
254		seq    Sequence
255		action string
256	}{
257		{seq: Sequence{{Sym: "]"}}, action: "single-bracket"},
258		{seq: Sequence{{Sym: "]"}, {Sym: "]"}, {Sym: "x"}}, action: "triple-bracket"},
259	} {
260		if err := trie.Insert(binding.seq, binding.action); err != nil {
261			t.Fatalf("Insert(%v) error = %v", binding.seq, err)
262		}
263	}
264
265	m := NewMatcher(trie)
266	m.Reset()
267
268	result := m.Feed(Key{Sym: "]"})
269	if result.Kind != ResultPending {
270		t.Fatalf("after ] Feed kind = %v, want %v", result.Kind, ResultPending)
271	}
272	if !m.Ambiguous() {
273		t.Fatal("Ambiguous() after ] = false, want true")
274	}
275
276	result = m.Feed(Key{Sym: "]"})
277	if result.Kind != ResultPending {
278		t.Fatalf("after ]] Feed kind = %v, want %v", result.Kind, ResultPending)
279	}
280	if m.Ambiguous() {
281		t.Fatal("Ambiguous() after ]] = true, want false")
282	}
283}
284
285func TestMatcher_AmbiguousShortLong(t *testing.T) {
286	m := NewMatcher(testTrie(t))
287
288	m.Reset()
289	result := m.Feed(Key{Sym: "]"})
290	if result.Kind != ResultPending {
291		t.Fatalf("Feed ] kind = %v, want %v", result.Kind, ResultPending)
292	}
293	if !m.Ambiguous() {
294		t.Fatal("Ambiguous() = false, want true after ]")
295	}
296
297	short, ok := m.ResolveAmbiguity()
298	if !ok {
299		t.Fatal("ResolveAmbiguity() ok = false, want true")
300	}
301	if short.Kind != ResultComplete {
302		t.Fatalf("ResolveAmbiguity() kind = %v, want %v", short.Kind, ResultComplete)
303	}
304	if short.Action != "single-bracket" {
305		t.Fatalf("short action = %q, want %q", short.Action, "single-bracket")
306	}
307
308	m.Reset()
309	_ = m.Feed(Key{Sym: "]"})
310	finish := m.Feed(Key{Sym: "]"})
311	if finish.Kind != ResultComplete {
312		t.Fatalf("finish ]] kind = %v, want %v", finish.Kind, ResultComplete)
313	}
314	if finish.Action != "double-bracket" {
315		t.Fatalf("long action = %q, want %q", finish.Action, "double-bracket")
316	}
317}
318
319func TestMatcher_Reset(t *testing.T) {
320	m := NewMatcher(testTrie(t))
321	_ = m.Feed(Key{Sym: "y"})
322	_ = m.Feed(Key{Sym: "a"})
323	m.Reset()
324	if m.Pending() != "" {
325		t.Fatalf("Pending after Reset = %q, want empty", m.Pending())
326	}
327	if m.Ambiguous() {
328		t.Fatal("Ambiguous() after Reset = true, want false")
329	}
330}
331
332func TestMatcher_InvalidMidSequence(t *testing.T) {
333	m := NewMatcher(testTrie(t))
334	m.Reset()
335	_ = m.Feed(Key{Sym: "y"})
336	result := m.Feed(Key{Sym: "x"})
337	if result.Kind != ResultInvalid {
338		t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultInvalid)
339	}
340}
341
342func TestMatcher_ResolveAmbiguityInvalid(t *testing.T) {
343	m := NewMatcher(testTrie(t))
344	m.Reset()
345	_, ok := m.ResolveAmbiguity()
346	if ok {
347		t.Fatal("ResolveAmbiguity without prefix ok = true, want false")
348	}
349}
350
351func TestMatcher_NilTriePendingEmpty(t *testing.T) {
352	m := NewMatcher(nil)
353	if got := m.Pending(); got != "" {
354		t.Fatalf("Pending() = %q, want empty", got)
355	}
356}
357
358func TestMatcher_NilTrieAmbiguousFalse(t *testing.T) {
359	m := NewMatcher(nil)
360	if m.Ambiguous() {
361		t.Fatal("Ambiguous() = true, want false")
362	}
363}
364
365func TestMatcher_NilTrieFeedInvalid(t *testing.T) {
366	m := NewMatcher(nil)
367
368	for _, key := range []Key{{Sym: "x"}, {Sym: "2"}} {
369		result := m.Feed(key)
370		if result.Kind != ResultInvalid {
371			t.Fatalf("Feed(%v) kind = %v, want %v", key, result.Kind, ResultInvalid)
372		}
373		if result.Pending != "" {
374			t.Fatalf("Feed(%v) pending = %q, want empty", key, result.Pending)
375		}
376		if got := m.Pending(); got != "" {
377			t.Fatalf("after Feed(%v) Pending() = %q, want empty", key, got)
378		}
379		if m.Ambiguous() {
380			t.Fatalf("after Feed(%v) Ambiguous() = true, want false", key)
381		}
382	}
383}
384
385func TestMatcher_InvalidDeadEnd(t *testing.T) {
386	trie := NewTrie()
387	trie.root.children[Key{Sym: "x"}] = newTrieNode()
388	m := NewMatcher(trie)
389	result := m.Feed(Key{Sym: "x"})
390	if result.Kind != ResultInvalid {
391		t.Fatalf("Feed kind = %v, want %v", result.Kind, ResultInvalid)
392	}
393}
394
395func TestMatcher_CountExactCap(t *testing.T) {
396	m := NewMatcher(testTrie(t))
397	m.Reset()
398	var result Result
399	for _, ch := range "9999" {
400		result = m.Feed(Key{Sym: string(ch)})
401	}
402	result = m.Feed(Key{Sym: "j"})
403	if result.Count != 9999 {
404		t.Fatalf("Count = %d, want 9999", result.Count)
405	}
406}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close