aboutsummaryrefslogtreecommitdiff

title: "ANN: remembering - Add memory to dmenu, fzf and similar tools"

date: 2021-01-26

layout: post

lang: en

ref: ann-remembering-add-memory-to-dmenu-fzf-and-similar-tools


Today I pushed v0.1.0 of remembering, a tool to enhance the interactive usability of menu-like tools, such as dmenu and fzf.

Previous solution

I previously used yeganesh to fill this gap, but as I started to rely less on Emacs, I added fzf as my go-to tool for doing fuzzy searching on the terminal. But I didn't like that fzf always showed the same order of things, when I would only need 3 or 4 commonly used files.

For those who don't know: yeganesh is a wrapper around dmenu that will remember your most used programs and put them on the beginning of the list of executables. This is very convenient for interactive prolonged use, as with time the things you usually want are right at the very beginning.

But now I had this thing, yeganesh, that solved this problem for dmenu, but didn't for fzf.

I initially considered patching yeganesh to support it, but I found it more coupled to dmenu than I would desire. I'd rather have something that knows nothing about dmenu, fzf or anything, but enhances tools like those in a useful way.

Implementation

Other than being decoupled from dmenu, another improvement I though that could be made on top of yeganesh is the programming language choice. Instead of Haskell, I went with POSIX sh. Sticking to POSIX sh makes it require less build-time dependencies. There aren't any, actually. Packaging is made much easier due to that.

The good thing is that the program itself is small enough (119 lines on v0.1.0) that POSIX sh does the job just fine, combined with other POSIX utilities such as getopts, sort and awk.

The behaviour is: given a program that will read from STDIN and write a single entry to STDOUT, remembering wraps that program, and rearranges STDIN so that previous choices appear at the beginning.

Where you would do:

$ seq 5 | fzf

  5
  4
  3
  2
> 1
  5/5
>

And every time get the same order of numbers, now you can write:

$ seq 5 | remembering -p seq-fzf -c fzf

  5
  4
  3
  2
> 1
  5/5
>

On the first run, everything is the same. If you picked 4 on the previous example, the following run would be different:

$ seq 5 | remembering -p seq-fzf -c fzf

  5
  3
  2
  1
> 4
  5/5
>

As time passes, the list would adjust based on the frequency of your choices.

I aimed for reusability, so that I could wrap diverse commands with remembering and it would be able to work. To accomplish that, a "profile" (the -p something part) stores data about different runs separately.

I took the idea of building something small with few dependencies to other places too: - the manpages are written in troff directly; - the tests are just more POSIX sh files; - and a POSIX Makefile to check and install.

I was aware of the value of sticking to coding to standards, but I had past experience mostly with programming language standards, such as ECMAScript, Common Lisp, Scheme, or with IndexedDB or DOM APIs. It felt good to rediscover these nice POSIX tools, which makes me remember of a quote by Henry Spencer:

Those who do not understand Unix are condemned to reinvent it, poorly.

Usage examples

Here are some functions I wrote myself that you may find useful:

Run a command with fzf on $PWD

f() {
  profile="$f-shell-function(pwd | sed -e 's_/_-_g')"
  file="$(git ls-files | \
            remembering -p "$profile" \
                        -c "fzf --select-1 --exit -0 --query \"$2\" --preview 'cat {}'")"
  if [ -n "$file" ]; then
    # shellcheck disable=2068
    history -s f $@
    history -s "$1" "$file"
  "$1" "$file"
fi
}

This way I can run f vi or f vi config at the root of a repository, and the list of files will always appear on the most used order. Adding pwd to the profile allows it to not mix data for different repositories.

Copy password to clipboard

choice="$(find "$HOME/.password-store" -type f                  | \
            grep -Ev '(.git|.gpg-id)'                           | \
            sed -e "s|$HOME/.password-store/||" -e 's/\.gpg$//' | \
            remembering -p password-store \
                        -c 'dmenu -l 20 -i')"


if [ -n "$choice" ]; then
  pass show "$choice" -c
fi

Adding the above to a file and binding it to a keyboard shortcut, I can access the contents of my password store, with the entries ordered by usage.

Replacing yeganesh

Where I previously had:

exe=$(yeganesh -x) && exec $exe

Now I have:

exe=$(dmenu_path | remembering -p dmenu-exec -c dmenu) && exec $exe

This way, the executables appear on order of usage.

If you don't have dmenu_path, you can get just the underlying stest tool that looks at the executables available in your $PATH. Here's a juicy one-liner to do it:

$ wget -O- https://dl.suckless.org/tools/dmenu-5.0.tar.gz | \
    tar Ozxf - dmenu-5.0/arg.h dmenu-5.0/stest.c          | \
    sed 's|^#include "arg.h"$|// #include "arg.h"|'       | \
    cc -xc - -o stest

With the stest utility you'll be able to list executables in your $PATH and pipe them to dmenu or something else yourself:

$ (IFS=:; ./stest -flx $PATH;) | sort -u | remembering -p another-dmenu-exec -c dmenu | sh

In fact, the code for dmenu_path is almost just like that.

Conclusion

For my personal use, I've packaged remembering for GNU Guix and Nix. Packaging it to any other distribution should be trivial, or just downloading the tarball and running [sudo] make install.

Patches welcome!