todo.txt and fzf

This post is about offering an interactive interface to todo.txt on the command line. Skip the first three paragraphs if you don’t give a crap about the history.

todo.txt is pretty close to perfect software. It’s plain text, meaning that hundreds of well-understood, fast, and venerable plain text manipulation tools work on it; it’s simple, requiring no more than a text editor to use; it’s well-documented; and the rules are easily remembered. It’s been around long enough that dozens of tools have been written to work with todo.txt files – GUIs for people who want check boxes and drop-down menus, command-line utilities, mobile apps, web apps. The core command-line client, todo.txt-cli, has its first git commit in 2009 by Gina Trapani who, I think(?) was the person who came up with the format. It’s simply a well-thought-out bit of kit, and I’m humbled whenever I think about the genius that created it.

Software being software, there have been a few ncurses implementations over the years, one of which I really liked but which started to suffer from bitrot and a couple of times corrupted my main todo.txt file. Consequently, I’d just been using the command line script for the past few months, but recently I started playing around with fzf as an action, and I’m really liking where it’s at.

The command-line client has a sort of plug-in system where it looks for a shell script in a directory if it’s passed any command it doesn’t recognize. The trivial plug-in is a two-line shell script:

#!/bin/bash
todo.sh ls | head -n -2 | fzf

All this does is pipe the output of todo.sh to fzf, stripping off the header for convenience. But fzf can do so much more! In particular, you can bind keys to actions in fzf, and while the resulting command is undeniably ugly and nearly impossible to read, the result is fantastic. Here’s the end result:

#!/bin/bash

case $1 in
	"usage")
            echo "todo.sh fzf, select and press ^x to make a task as complete"
            ;;
        *)
            todo.sh ls | head -n -2 | fzf --bind 'alt-x:execute(echo {} | cut -f1 -d" " | xargs todo.sh do)+reload(todo.sh ls | head -n -2),alt-r:reload(todo.sh ls | head -n -2),alt-s:execute(todo.sh sort)+reload(todo.sh ls | head -n -2),alt-n:execute(N=""; vared N; todo.sh add "$N")+reload(todo.sh ls | head -n -2),alt-e:execute(L=`echo {} | cut -d" " -f1`; E=`echo {} | cut -d" " -f2-`; vared E; sed -i "${TODO_FILE}" -e "${L}c\\${E}")+reload(todo.sh ls | head -n -2)'
            ;;
esac

As you can see, the fzf command got a little crazy. It binds four keystrokes (search for alt-\w), and while I’m not going to explain everything that’s going on – the fzf manpage is fine for that – I will explain what the bindings do:

For me, this is 99% of what I do with todo.txt: add, view, and complete, with a couple of utility functions. fzf makes a nice interface to viewing and filtering the list, and the bindings provide a fast and simple way to make minor changes. There are some failure cases – not everything is quoted out, and I’m sure I could create a task that breaks the edit command – but it’s highly unlikely I’m going to run into those. YMMV.

It demonstrates the power of uncomplex designs and simple formats, and the capabilities you gain by having an entire generation of text processing tools at the ready.

Update 2022-10-03 #

My current config adds alt-a to run archive, because why not?

   ...,alt-a:execute(todo.sh archive)+reload(todo.sh ls | head -n -2)'

Also, I actually added another action, noh, that just does todo.sh ls | head -n -2 and allows me to reduce all of the reloads to todo.sh noh; it makes things a little cleaner, but isn’t necessary.