Fuzzy Go Documentation Browser
In this blog post, I will go through very simple steps to create a fuzzy documentation browsing tool for Go stdlib using fzf.
Table of Contents
TLDR
If you are too lazy to follow this tutorial, source code is at https://github.com/diwasrimal/fuzzy-go-docs.git
Requirements
We will need fzf and optionally bat
for syntax highlight.
View package level documentation
We can list all packages in stdlib using the command
$ go list std
archive/tar
archive/zip
bufio
bytes
cmp
compress/bzip2
compress/flate
compress/gzip
compress/lzw
compress/zlib
...
Now if we pipe this list into fzf
, we can fuzzily search a package name.
To preview the documentation on the side, we can provide the --preview
option for fzf
.
We can use {}
to capture the matched package name. So
$ go list std | fzf --preview 'go doc {}'
will show documentation of the matched go package.
The documentation requires a lot of horizontal space, while the list of packages do not. So we can make the size of preview window larger using.
$ go list std | fzf --preview 'go doc {}' --preview-window 'right:70%'
The package documentation can get long, so to navigate the preview text effectively, we can bind
keys for scrolling by passing --bind
option to fzf
. We also enable moving to the first match
when query (what we type) changes.
$ go list std | fzf --preview 'go doc {}' --preview-window 'right:70%' --bind 'ctrl-j:preview-half-page-down,ctrl-k:preview-half-page-up' --bind "change:first"
Now we can use ctrl-j to scroll down and ctrl-k to scroll up the documentation on right side.
View symbol documentation inside package
Till now we were browsing packages and viewing their docs on right side. Now we want to “select” a package and view the docs for symbols contained inside the selected package. For this we bind tab and shift-tab keys to go inside and come outside of inner package i.e. symbol level docs.
Using Tab to browse package.symbol
When we hit tab we want the following to happen:
- fzf’s input should be replaced by package’s documentation
- reset the query (fzf’s input)
- change the preview command to show `‘go doc pkg.symbol’
- move preview window from right to bottom to show more text
To replace input, we use reload(...)
action from fzf. We bind tab such that it sets selected package
docs as fzf input. Then we can fuzzy find over individual symbols from the package.
To reset the query, use use change-query()
and set it to nothing.
To change the preview command, we use change-preview(...)
. But we have to extract the symbol from the doc line.
For this we can utilize an external script.
#!/bin/sh
# This script extracts the symbol from a go doc line
# Example:
# func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error - ServeTLS
# type CookieJar interface{ ... } - CookieJar
echo $@ | awk '{ print $2 }' | sed 's/[(\[].*$//'
Save this file as godoc-sym-extractor.sh
, put it somewhere in your path and make it executable.
While looking at symbol level docs, we store the currently browsing pkg name in a file, so that
it can be used in preview command as go doc <pkg>.{}
.
$ go list std \
| fzf --preview 'go doc {}' \
--preview-window 'right:70%' \
--bind 'ctrl-j:preview-half-page-down,ctrl-k:preview-half-page-up' \
--bind "change:first" \
--bind "tab:reload(echo {} > browsing_pkg.txt && go doc {} | grep -E '^(var|func|type|const) ')+change-query()+change-preview(go doc \$(cat browsing_pkg.txt).\$(godoc-sym-extractor.sh {}))+change-preview-window(bottom:60%)"
Using Shift Tab to browse packages again
To view browse packages again we can map shift-tab to reload the fzf input again, and set all the options that were set during the initial run.
$ go list std \
| fzf --preview 'go doc {}' \
--preview-window 'right:70%' \
--bind 'ctrl-j:preview-half-page-down,ctrl-k:preview-half-page-up' \
--bind "change:first" \
--bind "tab:reload(echo {} > browsing_pkg.txt && go doc {} | grep -E '^(var|func|type|const) ')+change-query()+change-preview(go doc \$(cat browsing_pkg.txt).\$(godoc-sym-extractor.sh {}))+change-preview-window(bottom:60%)" \
--bind "shift-tab:reload(go list std)+change-query()+change-preview(go doc {})+change-preview-window(right:70%)"
Make it fast, caching docs
Instead of using go doc
every time, it would be faster to just read from a file. In this step we make our script
run faster by generating the docs we need i.e. caching, and just reading the file later instead of using go doc
.
For this we use the below script.
#!/bin/sh
# Creates a go stdlib documentation cache
db=${GOPATH:-$HOME/.cache}/go-std-doc-cache
pkgs=$(go list std)
echo "$pkgs" > "$db/_pkgs.txt"
for pkg in $pkgs; do
mkdir -p $db/$pkg
go doc $pkg > "$db/$pkg/_doc.txt"
grep -E '^(var|func|type|const) ' "$db/$pkg/_doc.txt" > "$db/$pkg/_signatures.txt"
symbols=$(awk '{ print $2 }' "$db/$pkg/_signatures.txt" | sed 's/[(\[].*$//')
for sym in $symbols; do
go doc "$pkg.$sym" > "$db/$pkg/$sym.txt"
done
echo "written $db/$pkg"
done
After that we just have to modify our previous command to read from the cache, instead of using go doc
.
We can now use this script.
#!/bin/sh
cat="cat" # or "bat --language go --color always --plain" for syntax highlighting
db=${GOPATH:-$HOME/.cache}/go-std-doc-cache
save_browsing_pkg="echo {} > $db/browsing_pkg.txt"
get_browsing_pkg="cat $db/browsing_pkg.txt"
sym_extractor="godoc-sym-extractor.sh"
cat "$db/_pkgs.txt" \
| fzf --preview "$cat $db/{}/_doc.txt" \
--preview-window "right:70%" \
--bind "ctrl-j:preview-half-page-down,ctrl-k:preview-half-page-up" \
--bind "change:first" \
--bind "tab:reload($save_browsing_pkg && cat $db/{}/_signatures.txt)+change-preview($cat $db/\$($get_browsing_pkg)/\$($sym_extractor {}).txt)+change-query()+change-preview-window(bottom:60%)" \
--bind "shift-tab:reload(cat $db/_pkgs.txt)+change-query()+change-preview($cat $db/{}/_doc.txt)+change-preview-window(right:70%)"
Save this file as fzgd
(fuzzy go docs) somewhere in your path and make it executable. And just run
$ fzgd
How I am using it
$ tree ~/.local/bin
├── ...
├── fzgd
└── fzgd-extract-sym-from-line
fzgd
is a little modified from the one I mentioned above, allowing cache updating using fzgd -u
,
(see https://github.com/diwasrimal/fuzzy-go-docs).
fzgd-extract-sym-from-line
is the same as godoc-sym-extractor.sh
I mentioned above.