main 5041353

vimkeys_bridge_test.go RawHistoryFolder

1package input
2
3import (
4	"testing"
5
6	"github.com/bnema/dumber/internal/domain/vimkeys"
7	"github.com/bnema/puregotk/v4/gdk"
8)
9
10func TestKeyvalToVimKey(t *testing.T) {
11	tests := []struct {
12		name   string
13		keyval uint
14		state  gdk.ModifierType
15		want   vimkeys.Key
16		wantOk bool
17	}{
18		{
19			name:   "braceright with shift encodes glyph without shift mod",
20			keyval: uint(gdk.KEY_braceright),
21			state:  gdk.ShiftMaskValue,
22			want:   vimkeys.Key{Sym: "}"},
23			wantOk: true,
24		},
25		{
26			name:   "uppercase J with shift keeps letter and shift",
27			keyval: uint(gdk.KEY_J),
28			state:  gdk.ShiftMaskValue,
29			want:   vimkeys.Key{Sym: "j", Mods: vimkeys.ModShift},
30			wantOk: true,
31		},
32		{
33			name:   "lowercase j without mods",
34			keyval: uint(gdk.KEY_j),
35			state:  gdk.NoModifierMaskValue,
36			want:   vimkeys.Key{Sym: "j"},
37			wantOk: true,
38		},
39		{
40			name:   "named Escape keeps ctrl modifier",
41			keyval: uint(gdk.KEY_Escape),
42			state:  gdk.ControlMaskValue,
43			want:   vimkeys.Key{Sym: "Esc", Mods: vimkeys.ModCtrl},
44			wantOk: true,
45		},
46		{
47			name:   "named Return keeps shift modifier",
48			keyval: uint(gdk.KEY_Return),
49			state:  gdk.ShiftMaskValue,
50			want:   vimkeys.Key{Sym: "CR", Mods: vimkeys.ModShift},
51			wantOk: true,
52		},
53		{
54			name:   "named KP_Enter maps to CR",
55			keyval: uint(gdk.KEY_KP_Enter),
56			state:  gdk.NoModifierMaskValue,
57			want:   vimkeys.Key{Sym: "CR"},
58			wantOk: true,
59		},
60		{
61			name:   "named Space keeps alt modifier",
62			keyval: uint(gdk.KEY_space),
63			state:  gdk.AltMaskValue,
64			want:   vimkeys.Key{Sym: "Space", Mods: vimkeys.ModAlt},
65			wantOk: true,
66		},
67		{
68			name:   "named Tab maps to Tab",
69			keyval: uint(gdk.KEY_Tab),
70			state:  gdk.NoModifierMaskValue,
71			want:   vimkeys.Key{Sym: "Tab"},
72			wantOk: true,
73		},
74		{
75			name:   "named ISO_Left_Tab maps to Tab",
76			keyval: uint(gdk.KEY_ISO_Left_Tab),
77			state:  gdk.ShiftMaskValue,
78			want:   vimkeys.Key{Sym: "Tab", Mods: vimkeys.ModShift},
79			wantOk: true,
80		},
81		{
82			name:   "named BackSpace is BackSpace not BS",
83			keyval: uint(gdk.KEY_BackSpace),
84			state:  gdk.NoModifierMaskValue,
85			want:   vimkeys.Key{Sym: "BackSpace"},
86			wantOk: true,
87		},
88		{
89			name:   "named Delete is Delete not Del",
90			keyval: uint(gdk.KEY_Delete),
91			state:  gdk.ControlMaskValue,
92			want:   vimkeys.Key{Sym: "Delete", Mods: vimkeys.ModCtrl},
93			wantOk: true,
94		},
95		{
96			name:   "named Left keeps ctrl",
97			keyval: uint(gdk.KEY_Left),
98			state:  gdk.ControlMaskValue,
99			want:   vimkeys.Key{Sym: "Left", Mods: vimkeys.ModCtrl},
100			wantOk: true,
101		},
102		{
103			name:   "named Right",
104			keyval: uint(gdk.KEY_Right),
105			state:  gdk.NoModifierMaskValue,
106			want:   vimkeys.Key{Sym: "Right"},
107			wantOk: true,
108		},
109		{
110			name:   "named Up",
111			keyval: uint(gdk.KEY_Up),
112			state:  gdk.NoModifierMaskValue,
113			want:   vimkeys.Key{Sym: "Up"},
114			wantOk: true,
115		},
116		{
117			name:   "named Down",
118			keyval: uint(gdk.KEY_Down),
119			state:  gdk.NoModifierMaskValue,
120			want:   vimkeys.Key{Sym: "Down"},
121			wantOk: true,
122		},
123		{
124			name:   "named Home",
125			keyval: uint(gdk.KEY_Home),
126			state:  gdk.NoModifierMaskValue,
127			want:   vimkeys.Key{Sym: "Home"},
128			wantOk: true,
129		},
130		{
131			name:   "named End",
132			keyval: uint(gdk.KEY_End),
133			state:  gdk.NoModifierMaskValue,
134			want:   vimkeys.Key{Sym: "End"},
135			wantOk: true,
136		},
137		{
138			name:   "named PageUp",
139			keyval: uint(gdk.KEY_Page_Up),
140			state:  gdk.NoModifierMaskValue,
141			want:   vimkeys.Key{Sym: "PageUp"},
142			wantOk: true,
143		},
144		{
145			name:   "named PageDown",
146			keyval: uint(gdk.KEY_Page_Down),
147			state:  gdk.AltMaskValue,
148			want:   vimkeys.Key{Sym: "PageDown", Mods: vimkeys.ModAlt},
149			wantOk: true,
150		},
151		{
152			name:   "ctrl+d printable letter keeps ctrl",
153			keyval: uint(gdk.KEY_d),
154			state:  gdk.ControlMaskValue,
155			want:   vimkeys.Key{Sym: "d", Mods: vimkeys.ModCtrl},
156			wantOk: true,
157		},
158		{
159			name:   "ctrl+eacute Latin-1 keyval round-trips through parser",
160			keyval: uint(gdk.KEY_eacute),
161			state:  gdk.ControlMaskValue,
162			want:   vimkeys.Key{Sym: "é", Mods: vimkeys.ModCtrl},
163			wantOk: true,
164		},
165		{
166			name:   "non-printable rejected",
167			keyval: 0,
168			state:  gdk.NoModifierMaskValue,
169			wantOk: false,
170		},
171	}
172
173	for _, tt := range tests {
174		t.Run(tt.name+"_roundtrip_string", func(t *testing.T) {
175			if !tt.wantOk {
176				return
177			}
178			got, ok := KeyvalToVimKey(tt.keyval, tt.state)
179			if !ok {
180				t.Fatalf("KeyvalToVimKey() ok = false")
181			}
182			canonical := got.String()
183			parsed, err := vimkeys.ParseBinding(canonical)
184			if err != nil {
185				t.Fatalf("ParseBinding(%q) error = %v", canonical, err)
186			}
187			if !parsed.Equal(vimkeys.Sequence{got}) {
188				t.Fatalf("round-trip %q -> %v, want %v", canonical, parsed, vimkeys.Sequence{got})
189			}
190		})
191	}
192
193	for _, tt := range tests {
194		t.Run(tt.name, func(t *testing.T) {
195			got, ok := KeyvalToVimKey(tt.keyval, tt.state)
196			if ok != tt.wantOk {
197				t.Fatalf("KeyvalToVimKey() ok = %v, want %v", ok, tt.wantOk)
198			}
199			if !tt.wantOk {
200				return
201			}
202			if got != tt.want {
203				t.Fatalf("KeyvalToVimKey() = %#v, want %#v", got, tt.want)
204			}
205		})
206	}
207}

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