v0.23.2 bfd5eb4

search_test.go RawHistoryFolder

1package url
2
3import "testing"
4
5// Test shortcuts for BuildSearchURL tests
6var testShortcuts = map[string]string{
7	"g":   "https://google.com/search?q=%s",
8	"ddg": "https://duckduckgo.com/?q=%s",
9	"gh":  "https://github.com/search?q=%s",
10	"gi":  "https://google.com/search?tbm=isch&q=%s",
11}
12
13const testDefaultSearch = "https://duckduckgo.com/?q=%s"
14
15func TestBuildSearchURL(t *testing.T) {
16	tests := []struct {
17		name          string
18		input         string
19		shortcuts     map[string]string
20		defaultSearch string
21		want          string
22	}{
23		{
24			name:          "bang shortcut google",
25			input:         "!g golang",
26			shortcuts:     testShortcuts,
27			defaultSearch: testDefaultSearch,
28			want:          "https://google.com/search?q=golang",
29		},
30		{
31			name:          "bang shortcut duckduckgo multi-word",
32			input:         "!ddg rust async await",
33			shortcuts:     testShortcuts,
34			defaultSearch: testDefaultSearch,
35			want:          "https://duckduckgo.com/?q=rust async await",
36		},
37		{
38			name:          "bang shortcut google images",
39			input:         "!gi cats",
40			shortcuts:     testShortcuts,
41			defaultSearch: testDefaultSearch,
42			want:          "https://google.com/search?tbm=isch&q=cats",
43		},
44		{
45			name:          "unknown bang falls back to default search",
46			input:         "!unknown test query",
47			shortcuts:     testShortcuts,
48			defaultSearch: testDefaultSearch,
49			want:          "https://duckduckgo.com/?q=!unknown test query",
50		},
51		{
52			name:          "url-like input gets normalized",
53			input:         "example.com",
54			shortcuts:     testShortcuts,
55			defaultSearch: testDefaultSearch,
56			want:          "https://example.com",
57		},
58		{
59			name:          "url with path",
60			input:         "github.com/user/repo",
61			shortcuts:     testShortcuts,
62			defaultSearch: testDefaultSearch,
63			want:          "https://github.com/user/repo",
64		},
65		{
66			name:          "url with scheme unchanged",
67			input:         "https://example.com",
68			shortcuts:     testShortcuts,
69			defaultSearch: testDefaultSearch,
70			want:          "https://example.com",
71		},
72		{
73			name:          "custom scheme dumb unchanged",
74			input:         "dumb://settings",
75			shortcuts:     testShortcuts,
76			defaultSearch: testDefaultSearch,
77			want:          "dumb://settings",
78		},
79		{
80			name:          "plain search query uses default",
81			input:         "how to parse json",
82			shortcuts:     testShortcuts,
83			defaultSearch: testDefaultSearch,
84			want:          "https://duckduckgo.com/?q=how to parse json",
85		},
86		{
87			name:          "empty input returns empty",
88			input:         "",
89			shortcuts:     testShortcuts,
90			defaultSearch: testDefaultSearch,
91			want:          "",
92		},
93		{
94			name:          "no shortcuts uses default for bang",
95			input:         "!g test",
96			shortcuts:     nil,
97			defaultSearch: testDefaultSearch,
98			want:          "https://duckduckgo.com/?q=!g test",
99		},
100		{
101			name:          "no default search returns input for plain query",
102			input:         "plain query",
103			shortcuts:     testShortcuts,
104			defaultSearch: "",
105			want:          "plain query",
106		},
107		{
108			name:          "bang without query treated as search",
109			input:         "!g",
110			shortcuts:     testShortcuts,
111			defaultSearch: testDefaultSearch,
112			want:          "https://duckduckgo.com/?q=!g",
113		},
114	}
115
116	for _, tt := range tests {
117		t.Run(tt.name, func(t *testing.T) {
118			got := BuildSearchURL(tt.input, tt.shortcuts, tt.defaultSearch)
119			if got != tt.want {
120				t.Errorf("BuildSearchURL() = %q, want %q", got, tt.want)
121			}
122		})
123	}
124}
125
126func TestParseBangShortcut(t *testing.T) {
127	tests := []struct {
128		name         string
129		input        string
130		wantShortcut string
131		wantQuery    string
132		wantFound    bool
133	}{
134		{
135			name:         "simple google search",
136			input:        "!g golang",
137			wantShortcut: "g",
138			wantQuery:    "golang",
139			wantFound:    true,
140		},
141		{
142			name:         "duckduckgo multi-word query",
143			input:        "!ddg rust async await",
144			wantShortcut: "ddg",
145			wantQuery:    "rust async await",
146			wantFound:    true,
147		},
148		{
149			name:         "github search",
150			input:        "!gh opencode cli",
151			wantShortcut: "gh",
152			wantQuery:    "opencode cli",
153			wantFound:    true,
154		},
155		{
156			name:         "google images",
157			input:        "!gi cats",
158			wantShortcut: "gi",
159			wantQuery:    "cats",
160			wantFound:    true,
161		},
162		{
163			name:         "youtube search",
164			input:        "!yt music video",
165			wantShortcut: "yt",
166			wantQuery:    "music video",
167			wantFound:    true,
168		},
169		{
170			name:         "no query after shortcut",
171			input:        "!g",
172			wantShortcut: "",
173			wantQuery:    "",
174			wantFound:    false,
175		},
176		{
177			name:         "empty query after space",
178			input:        "!g ",
179			wantShortcut: "",
180			wantQuery:    "",
181			wantFound:    false,
182		},
183		{
184			name:         "multiple spaces only",
185			input:        "!g   ",
186			wantShortcut: "",
187			wantQuery:    "",
188			wantFound:    false,
189		},
190		{
191			name:         "empty shortcut",
192			input:        "! query",
193			wantShortcut: "",
194			wantQuery:    "",
195			wantFound:    false,
196		},
197		{
198			name:         "plain text no bang",
199			input:        "plain text",
200			wantShortcut: "",
201			wantQuery:    "",
202			wantFound:    false,
203		},
204		{
205			name:         "bang at end not supported",
206			input:        "query !g",
207			wantShortcut: "",
208			wantQuery:    "",
209			wantFound:    false,
210		},
211		{
212			name:         "url-like input",
213			input:        "example.com",
214			wantShortcut: "",
215			wantQuery:    "",
216			wantFound:    false,
217		},
218		{
219			name:         "empty input",
220			input:        "",
221			wantShortcut: "",
222			wantQuery:    "",
223			wantFound:    false,
224		},
225		{
226			name:         "just exclamation mark",
227			input:        "!",
228			wantShortcut: "",
229			wantQuery:    "",
230			wantFound:    false,
231		},
232		{
233			name:         "query with extra spaces trimmed",
234			input:        "!g   lots of spaces  ",
235			wantShortcut: "g",
236			wantQuery:    "lots of spaces",
237			wantFound:    true,
238		},
239		{
240			name:         "long shortcut key",
241			input:        "!stackoverflow how to parse json",
242			wantShortcut: "stackoverflow",
243			wantQuery:    "how to parse json",
244			wantFound:    true,
245		},
246	}
247
248	for _, tt := range tests {
249		t.Run(tt.name, func(t *testing.T) {
250			gotShortcut, gotQuery, gotFound := ParseBangShortcut(tt.input)
251			if gotShortcut != tt.wantShortcut {
252				t.Errorf("ParseBangShortcut() shortcut = %q, want %q", gotShortcut, tt.wantShortcut)
253			}
254			if gotQuery != tt.wantQuery {
255				t.Errorf("ParseBangShortcut() query = %q, want %q", gotQuery, tt.wantQuery)
256			}
257			if gotFound != tt.wantFound {
258				t.Errorf("ParseBangShortcut() found = %v, want %v", gotFound, tt.wantFound)
259			}
260		})
261	}
262}

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