Fzf As A TUI For Your Tools

2025-03-30

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:

function gswfzf() {
  if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
    branches="$(git branch -r -l --format="%(refname:short)")"
    selection=$(echo "$branches" | fzf)
    if [[ -n "$selection" ]]; then 
      echo "$selection" | perl -pe "s/(origin)//" | xargs git switch 
    fi
  else
    echo "Not in a git repository"
    return 1
  fi
}

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="$(git branch -r -l --format="%(refname:short)")"
selection=$(echo "$branches" | fzf)

Here I simply remove any remote name prefix, like origin/, using the Perl regex engine, and switch to the selected branch:

if [[ -n "$selection" ]]; then 
    echo "$selection" | perl -pe "s/(origin)//" | xargs git switch 
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.