Simple Menu TUIs using fzf

2023-03-11

*Links can be found below post*

If you hang around on nerdy Linux boards, tilde communities and the like for long at some point you'll see a reference to Vim, emacs, probably Neovim, i3, and what other acronyms? fzf is one I see a lot.

fzf is a "command-line fuzzy finder" by junegunn, and it's well-loved for good reason.

It's an interactive Unix filter for command-line that can be used with any list; files, command history, processes, hostnames, bookmarks, git commits, etc.

It works as an executable binary as well as embedded within tmux; with key bindings in the shell in Bash, Fish, and Zsh; as a Neo/Vim plugin. It's also easy to install and is available in the package repositories for a large number of Linux distros as well as Homebrew.

I have to admit I haven't used it nearly as much as many other people do. But recently while reading the community bulletin board called "iris" that is private for members of the Ctrl-C Club tilde server community I read this intriguing little comment from someone who built a tool for searching and navigating through old message threads.

They said that they had been looking for a way of building TUIs (text user interfaces) for shell scripts, and at first had considered using dialog, a very old program that makes use of the Curses library. But this person then found fzf very convenient because it let them build TUIs quickly, just by piping the options into fzf and saving the reply as a variable. They said they used it as a replacement for menus, yes/no boxes, lists, checklists (when using --multi option).

For the benefit of those newer to working on the command line, a Command Line Interface and Text User Interface are not the same thing. I think of the command line as the interactive text parser you interact with by giving a single line of text and hitting enter. You (usually) get text output, some text printed to the screen for example. Then you write another line, see its output, and continue refining and directing your experience this way. A TUI on the other hand is an interface 'between' the command line interface and a full-fledged graphical user interface. They may use color, characters that approximate a visual interface element (like using text lines to draw a border around a box), and the use of indicators like a pointer to show a user what has been selected. They may look like simple GUIs, but they're usually quite fast, often allow key-driven action, and they let the user stay in the Terminal without launching a new program. Examples of common TUI programs are Midnight Commander, a file manager, and Nano or Vim, text editors with light TUIs.

When I previously was looking for simple ways of making a TUI I had come across Bubbletea, a Go library that makes beautiful TUI programs. I'm not interested in pursuing Go and haven't come across any other libraries with some of the advanced features built into it. But I tried several other approaches: using the dialog library, with curses, or coding in a language with curses bindings. I have also tried manually approaching the task of creating a TUI menu by using `select` and looping through to parse user input for example. Recently I also found a thread with multiple answers on ASkUbuntu.com with some of these solutions and others such as using ANSI escape codes to color text, and other Bash script approaches. No doubt this can work for some folks, but I found the idea of using fzf to easily make selectable TUI menus really intriguing.

Sure enough, it was quick, fairly easy to use this system - summarized as fzf to select, output saved to a variable, which we then interpret. When using fzf you can press up or down (no vim-keys) to navigate through the menu options, or press the number of the entry number, or start typing other text in the menu item and hit enter to open it. To go "back" to the start of the menu, you delete your text. Exit out by pressing cancel, selecting Quit, or Ctrl-C.

After installing fzf, try it out by running fzf in your terminal. You'll have a simple file selector. This will let you check it out.

fzf code snippets for starter TUI menus

With the previous in mind, here's my own attempts at creating some simple basic starter snippets. More advanced usage can be found in the fzf wiki and other links at the end of this post.

Unless otherwise stated, these are written in fishscript for fish shell.

Minimal example: a simple yes/no dialog selector

fish
set INPUT $(echo -e "yes \nno" | fzf)

if [ $INPUT = "yes" ] ;
  echo "yes it is" # do YES
else
  echo "no it's not" #do NO
end

Explanation: You are printing yes and no, delimited with a newline \n, the default for fzf. This is getting piped into fzf, which renders a simple default 2 line menu, and saves the output of whichever is selected to the variable INPUT. You can then process this and perform an action, possibly with an if conditional, as demonstrated, or similar.

fzf has a number of styling options available, including border options (rounded, anyone?). To be honest, I'm relatively new to fzf and am likely underutilizing and understyling it now. Check out some of the articles and example programs with fzf below to learn better ways to use it in your shell scripts to make TUI menus.

Basic menu example: A recipe browser

This is a basic script I use for accessing my recipes on my computer in a directory.

# run this inside the recipe directory
ls | fzf --header"Use CTRL-C to cancel" --reverse --preview="cat {}"

I list the full path to the directory and pass in all of the recipes (txt files) in that directory to fzf, specifying a header, and to list the recipes in reverse (A-Z) and using cat to print out the contents of each recipe as I navigate through the folder.

Much more advanced uses of fzf to create TUI menus can be found linked below.

Links

fzf (GitHub)

Bubbletea TUI library for Go (GitHub)

Iris-search, by nodisc (Tildegit)

How can I create a select menu in a shell script? (AskUbuntu)

A Practice Guide to fzf: Building a File Explorer

Using fzf as a dmenu replacement

fzf wiki with many example uses

---

You can leave a comment by emailing lettuce at the ctrl-c club domain. Please put 'comment' in the subject and specify what post you are responding to and your handle.

---

Hello Lettuce!

Just a quick message to say thank you for this post on your gemlog. I

heard about fzf a long time ago but, a bit like you, I had never found

a useful function of it; well, on that suits me in my own workflow,

should I say. But your example for the recipes made me wondered. I

installed it and tried. I found it amazing!

I'm not a TUI person. Apart from pamix(1) I don't use TUIs at all.

But I have to say that I'll now use your example to browse my own

recipes. And, maybe, I could tweak it for some things else.

No thank to you for digging me in fzf rabbit hole :P

I wish you the best. Take care :)

-f6k

Thanks for sharing this! About a dozen years ago I built a little app using PHP, jQuery, and SQLite that runs in my

web browser to track my daily expenses. In recent years I spend less and less time in a desktop environment,

working more often in the Linux console. I've wanted to migrate my app but didn't want to figure out how to use

ncurses and\or dialog plus possibly PHP or nodeJS or some other scripting language to put all the pieces together.

I have long had fzf installed on all my machines but had never really learned to use it, it was just a quirky little

file finder and Ranger works so much better for me (largely because I took the time to learn to use it).

Your post about using fzf to build a TUI was just what I needed. The very next day I built a bash + fzf + SQLite

version of the app that works great on the various machines I've got laying around here. SyncThing keeps the

database synced between them all and I have my data everywhere I need it -- including that I can still use the

original web-based version.

-James Card