Fzf As A TUI For Your Tools
So recently I’ve been playing around with an idea of a CLI application, distributed as a GitHub CLI extension, that would allow me to quickly lookup the README of some repository that I’ve starred on GitHub without leaving the command line. I often find myself wanting to do this, for example when I’m working on my Neovim configuration or when I’m trying to remind myself of some command alias from a Zsh plugin.
In this post I want to write about how I’ve been approaching this using the command line fuzzy finder fzf.
# Fzf As A Git Picker
The fzf CLI is a great tool for searching a list of strings interactively and I’ve been using it for a while now as a picker for git branches in code bases at work.
For example, this simple bash function that I’ve aliased to a command in my shell configuration gives me a lot of value when I want to checkout a remote git branch that matches the name of some issue on my team’s Kanban board:
The commands below lists all the remote branches in the branches variable,
pipes it to fzf and finally captures the selected list item in the selection variable
using fzf.
branches=""
selection=
Here I simply remove any remote name prefix, like origin/, using the Perl regex engine, and switch to the selected branch:
if ; then
| |
fi
# Fzf’s CLI
Combined with the GitHub CLI gh, which allows you to easily send authenticated requests to the GitHub GraphQL API, I thought it would be interesting
to try and write a small bash program which would allow me to fuzzy search for a starred repository and then display its README in a pager or editor.
This resulted in me writing gh-starred, a multi-file bash script that uses fzf together with the GitHub CLI gh.
Before starting this project, I had the thought that writing this straight up in bash might not be the most
practical or ergonomic way to do this, but I saw it as a fun challenge to see if I could learn some new things about bash
in the process. It came to a point where I was reaching for something like a map data structure
in order to map GitHub repository names to GitHub object IDs. This led me to discover that
bash has associative arrays, which I did not previously know about.
Ultimately, I did not need to use associative arrays,
but instead I could make use of the excellent CLI that fzf provides through its options.
See, my approach was to display the repository information as tabular data,
with some obvious columns like nameWithOwner, description and url.
Fzf provides options like --nth=N, which allows you to specify which column
should be used in the fuzzy search algorithm. In my case that would be
the nameWithOwner column by default.
Now together with the option --delimiter=STR that instructs fzf how to
parse the columns in your tabular data, and the option --with-nth=N which
tells fzf how many of the columns you want to display in the TUI,
you can have hidden columns in each row, e.g. id.
When the user selects one of the rows in the fzf TUI, the value in the hidden column
of that row can then be captured (or another value from any other column for that matter)
and passed to another command in your script.
This is enabled through the highly flexible binding interface that fzf provides
with the --bind '<KEY|EVENT>:<ACTION>' option, where field (column) values can be
referenced in the ACTION syntax.
As the amount of combinations of arbitrary actions you can perform with this interface
is incredible generous, I will not cover many of them here. But for instance this allows
me to capture the internal GitHub id of the selected repository
and then use it to fetch the README of that repository through another call to the
GitHub GraphQL API and display it with an arbitrary shell command, using the
become() action among fzf’s multiple actions.
# Iterating on Fzf
My initial implementation of gh-starred simply uses these CLI bindings to synchronously
fetch data from the GitHub GraphQL API and update the TUI by invoking the fzf program again with new data.
However, this does not feel as snappy as I’d like a CLI application to feel like.
A solution to this issue that fzf seems to provide, is the ability to
start a HTTP server using the --listen=HTTP_PORT option, which allows you to POST
new data and update the TUI asynchronously.
This have inspired me to rewrite gh-starred in order improve upon the snappiness.
I also want to support pagination, which it correctly does not and is a big limitation.
Finally I would like to remove the dependency on jq, as this was something I used
mainly for my own ergonomics when developing the script.
I’ve been looking at babashka as an implementation candidate over bash,
as I’ve been trying to get into Clojure lately and I feel like the language provides ergonomic destructuring out-of-the-box,
similar to jq, in addition to allowing me (I think?) to compile the project to a single binary
using GraalVM native-image.
I hope this post has inspired you to try out fzf in your own CLI applications.
More on gh-starred in babashka perhaps in future blog posts.