v0.27.0 dcb4c51

floating_shortcuts_test.go RawHistoryFolder

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

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