internal/frontend: add search keyboard shortcuts

It is now possible to trigger symbol search mode with the prefixes "#"
and "s: ", and package search mode with "p: ".

For golang/go#44142

Change-Id: Idf803bab84855cd7d7a15862552e21eda3386577
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/346113
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 172e6d5..80e2250 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -260,6 +260,22 @@
 	searchModeSymbol = "symbol"
 )
 
+var (
+	// searchModeSymbolKeyboardShortcuts is the set of allow keyboard shortcuts
+	// for symbol search.
+	searchModeSymbolKeyboardShortcuts = map[string]bool{
+		"s":              true,
+		searchModeSymbol: true,
+	}
+
+	// searchModePackageKeyboardShortcuts is the set of allow keyboard shortcuts
+	// for package search.
+	searchModePackageKeyboardShortcuts = map[string]bool{
+		"p":               true,
+		searchModePackage: true,
+	}
+)
+
 // serveSearch applies database data to the search template. Handles endpoint
 // /search?q=<query>. If <query> is an exact match for a package path, the user
 // will be redirected to the details page.
@@ -368,12 +384,18 @@
 	if !experiment.IsActive(r.Context(), internal.ExperimentSymbolSearch) {
 		return q, false
 	}
-
-	if prefix := searchModeSymbol + ":"; strings.HasPrefix(q, prefix) {
-		return strings.TrimPrefix(q, prefix), true
+	if strings.HasPrefix(q, "#") {
+		return strings.TrimPrefix(q, "#"), true
 	}
-	if prefix := searchModePackage + ":"; strings.HasPrefix(q, prefix) {
-		return strings.TrimPrefix(q, prefix), false
+	if strings.Contains(q, ":") {
+		parts := strings.SplitN(q, ":", 2)
+		if searchModeSymbolKeyboardShortcuts[parts[0]] {
+			return parts[1], true
+		}
+		if searchModePackageKeyboardShortcuts[parts[0]] {
+			return parts[1], false
+		}
+		return q, false
 	}
 	if shouldDefaultToSymbolSearch(q) {
 		return q, true
diff --git a/tests/search/main.go b/tests/search/main.go
index 72575f4..2d1d567 100755
--- a/tests/search/main.go
+++ b/tests/search/main.go
@@ -123,7 +123,7 @@
 		}
 		if want.symbol != got.SymbolName || want.pkg != got.PackagePath || st.mode != searchPage.SearchMode {
 			output = append(output,
-				fmt.Sprintf("query %s, mismatch result %d:\n\twant: %q %q [m=%q]\n\t got: %q %q [m=%q]\n",
+				fmt.Sprintf("query: %q, mismatch result %d:\n\twant: %q %q [m=%q]\n\t got: %q %q [m=%q]\n",
 					st.query, i+1,
 					want.pkg, want.symbol, st.mode,
 					got.PackagePath, got.SymbolName, searchPage.SearchMode))
diff --git a/tests/search/scripts/default.txt b/tests/search/scripts/default.txt
index 097f5d8..92f7132 100644
--- a/tests/search/scripts/default.txt
+++ b/tests/search/scripts/default.txt
@@ -5,15 +5,41 @@
 # This file contains test scripts for queries and their default search mode.
 
 github.com defaults to package mode
-[package] github.com
+[] github.com
 github.com/go-openapi/strfmt
 github.com/beego/bee/cmd/commands/api
 github.com/julieqiu/api-demo/tar
 
 gopkg.in search defaults to package mode
-[package] gopkg.in
+[] gopkg.in
 gopkg.in/foo.v1
 
 gopkg.in element search defaults to package mode
-[package] foo.v1
+[] foo.v1
+gopkg.in/foo.v1
+
+keyboard shorcut "#"
+[symbol] #big.Float
+Float math/big
+
+keyboard shorcut "s:"
+[symbol] s:foo
+Foo gopkg.in/foo.v1
+FOO github.com/julieqiu/api-demo
+FoO github.com/julieqiu/api-demo
+Foo github.com/julieqiu/api-demo
+
+keyboard shorcut "symbol:"
+[symbol] s:foo
+Foo gopkg.in/foo.v1
+FOO github.com/julieqiu/api-demo
+FoO github.com/julieqiu/api-demo
+Foo github.com/julieqiu/api-demo
+
+keyboard shorcut "p:"
+[] p:Foo
+gopkg.in/foo.v1
+
+keyboard shorcut "package:"
+[] package:Foo
 gopkg.in/foo.v1