v0.33.2 57f21d6

floating_shortcuts_test.go RawHistoryFolder

1package input
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/domain/entity"
8	"github.com/bnema/puregotk/v4/gdk"
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13// defaultWorkspaceConfig returns a WorkspaceConfig with the default shortcuts,
14// mirroring the relevant portion of config.DefaultConfig().
15func defaultWorkspaceConfig() *entity.WorkspaceConfig {
16	return &entity.WorkspaceConfig{
17		Shortcuts: entity.GlobalShortcutsConfig{
18			Actions: map[string]entity.ActionBinding{
19				"toggle-floating-pane":   {Keys: []string{"alt+f"}},
20				"consume-or-expel-left":  {Keys: []string{"alt+["}},
21				"consume-or-expel-right": {Keys: []string{"alt+]"}},
22				"open-omnibox":           {Keys: []string{"ctrl+l"}},
23			},
24		},
25	}
26}
27
28func TestShortcutSet_GlobalToggleFloatingPane(t *testing.T) {
29	ws := defaultWorkspaceConfig()
30	set := NewShortcutSet(context.Background(), ws, nil)
31
32	action, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_f), Modifiers: ModAlt}, ModeNormal)
33	require.True(t, ok)
34	assert.Equal(t, ActionToggleFloatingPane, action)
35}
36
37func TestShortcutSet_FloatingProfiles_RegisterMultiple(t *testing.T) {
38	ws := defaultWorkspaceConfig()
39	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
40		"google": {
41			Keys: []string{"alt+g"},
42			URL:  "https://google.com",
43		},
44		"github": {
45			Keys: []string{"alt+y"},
46			URL:  "https://github.com",
47		},
48	}
49
50	set := NewShortcutSet(context.Background(), ws, nil)
51
52	googleAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_g), Modifiers: ModAlt}, ModeNormal)
53	require.True(t, ok)
54	googleTarget, ok := ParseFloatingProfileTarget(googleAction)
55	require.True(t, ok)
56	assert.Equal(t, "google", googleTarget.SessionID)
57	assert.Equal(t, "https://google.com", googleTarget.URL)
58
59	githubAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_y), Modifiers: ModAlt}, ModeNormal)
60	require.True(t, ok)
61	githubTarget, ok := ParseFloatingProfileTarget(githubAction)
62	require.True(t, ok)
63	assert.Equal(t, "github", githubTarget.SessionID)
64	assert.Equal(t, "https://github.com", githubTarget.URL)
65}
66
67func TestCollectFloatingProfileShortcuts_SkipsConflicts(t *testing.T) {
68	ws := defaultWorkspaceConfig()
69	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
70		"tab-conflict": {
71			Keys: []string{"alt+1"},
72			URL:  "https://example.com/tab",
73		},
74		"pane-conflict": {
75			Keys: []string{"alt+["},
76			URL:  "https://example.com/pane",
77		},
78		"ok": {
79			Keys: []string{"alt+g"},
80			URL:  "https://example.com/ok",
81		},
82	}
83
84	occupied := map[KeyBinding]Action{
85		{Keyval: uint(gdk.KEY_1), Modifiers: ModAlt}:           ActionSwitchTabIndex1,
86		{Keyval: uint(gdk.KEY_bracketleft), Modifiers: ModAlt}: ActionConsumeOrExpelLeft,
87	}
88
89	shortcuts := collectFloatingProfileShortcutsFromWorkspace(context.Background(), ws, occupied)
90	require.Len(t, shortcuts, 1)
91	assert.Equal(t, KeyBinding{Keyval: uint(gdk.KEY_g), Modifiers: ModAlt}, shortcuts[0].Binding)
92	target, ok := ParseFloatingProfileTarget(shortcuts[0].Action)
93	require.True(t, ok)
94	assert.Equal(t, "ok", target.SessionID)
95	assert.Equal(t, "https://example.com/ok", target.URL)
96}
97
98func TestShortcutSet_FloatingProfilesPreserveExistingGlobalShortcuts(t *testing.T) {
99	ws := defaultWorkspaceConfig()
100	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
101		"conflict": {
102			Keys: []string{"alt+["},
103			URL:  "https://example.com/override-attempt",
104		},
105	}
106
107	set := NewShortcutSet(context.Background(), ws, nil)
108
109	tabAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_bracketleft), Modifiers: ModAlt}, ModeNormal)
110	require.True(t, ok)
111	assert.Equal(t, ActionConsumeOrExpelLeft, tabAction)
112
113	omniboxAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_l), Modifiers: ModCtrl}, ModeNormal)
114	require.True(t, ok)
115	assert.Equal(t, ActionOpenOmnibox, omniboxAction)
116}
117
118func TestShortcutSet_FloatingProfilesSkipGlobalOnlyConflicts(t *testing.T) {
119	ws := defaultWorkspaceConfig()
120	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
121		"alt-one-conflict": {
122			Keys: []string{"alt+1"},
123			URL:  "https://example.com/one",
124		},
125		"alt-tab-conflict": {
126			Keys: []string{"alt+tab"},
127			URL:  "https://example.com/tab",
128		},
129		"ok": {
130			Keys: []string{"alt+g"},
131			URL:  "https://example.com/ok",
132		},
133	}
134
135	set := NewShortcutSet(context.Background(), ws, nil)
136
137	_, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_1), Modifiers: ModAlt}, ModeNormal)
138	assert.False(t, ok)
139
140	_, ok = set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_Tab), Modifiers: ModAlt}, ModeNormal)
141	assert.False(t, ok)
142
143	action, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_g), Modifiers: ModAlt}, ModeNormal)
144	require.True(t, ok)
145	target, ok := ParseFloatingProfileTarget(action)
146	require.True(t, ok)
147	assert.Equal(t, "ok", target.SessionID)
148	assert.Equal(t, "https://example.com/ok", target.URL)
149}
150
151func TestShortcutSet_FloatingProfilesWithSameURLGetDistinctSessionIDs(t *testing.T) {
152	ws := defaultWorkspaceConfig()
153	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
154		"work-mail": {
155			Keys: []string{"alt+w"},
156			URL:  "https://mail.google.com",
157		},
158		"personal-mail": {
159			Keys: []string{"alt+p"},
160			URL:  "https://mail.google.com",
161		},
162	}
163
164	set := NewShortcutSet(context.Background(), ws, nil)
165
166	workAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_w), Modifiers: ModAlt}, ModeNormal)
167	require.True(t, ok)
168	workTarget, ok := ParseFloatingProfileTarget(workAction)
169	require.True(t, ok)
170
171	personalAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_p), Modifiers: ModAlt}, ModeNormal)
172	require.True(t, ok)
173	personalTarget, ok := ParseFloatingProfileTarget(personalAction)
174	require.True(t, ok)
175
176	assert.Equal(t, "https://mail.google.com", workTarget.URL)
177	assert.Equal(t, "https://mail.google.com", personalTarget.URL)
178	assert.NotEqual(t, workTarget.SessionID, personalTarget.SessionID)
179}
180
181func TestShortcutSet_FloatingProfiles_SupportModifierCombos(t *testing.T) {
182	ws := defaultWorkspaceConfig()
183	ws.FloatingPane.Profiles = map[string]entity.FloatingPaneProfile{
184		"mail": {
185			Keys: []string{"ctrl+alt+m"},
186			URL:  "https://mail.example.com",
187		},
188		"search": {
189			Keys: []string{"ctrl+shift+y"},
190			URL:  "https://search.example.com",
191		},
192	}
193
194	set := NewShortcutSet(context.Background(), ws, nil)
195
196	mailAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_m), Modifiers: ModCtrl | ModAlt}, ModeNormal)
197	require.True(t, ok)
198	mailTarget, ok := ParseFloatingProfileTarget(mailAction)
199	require.True(t, ok)
200	assert.Equal(t, "mail", mailTarget.SessionID)
201	assert.Equal(t, "https://mail.example.com", mailTarget.URL)
202
203	searchAction, ok := set.Lookup(KeyBinding{Keyval: uint(gdk.KEY_y), Modifiers: ModCtrl | ModShift}, ModeNormal)
204	require.True(t, ok)
205	searchTarget, ok := ParseFloatingProfileTarget(searchAction)
206	require.True(t, ok)
207	assert.Equal(t, "search", searchTarget.SessionID)
208	assert.Equal(t, "https://search.example.com", searchTarget.URL)
209}
210
211func TestParseFloatingProfileTarget_AllowsPipeInURL(t *testing.T) {
212	action := NewFloatingProfileAction("pipe-test", "https://example.com/a|b")
213
214	target, ok := ParseFloatingProfileTarget(action)
215	require.True(t, ok)
216	assert.Equal(t, "pipe-test", target.SessionID)
217	assert.Equal(t, "https://example.com/a|b", target.URL)
218}

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