“Vanity jar”

I really like dmenu, which is a simple menu that takes input from stdin, allows the user to select one by typing, Tabbing or pointing, and prints it to stdout, to be processed by another program in the pipeline. Many people use it as a program launcher, but it can be used for just about anything.

I also really like fzf, which is a fuzzy finder – that is, a program similar to dmenu in that it interactively narrows a large number of items to one selection, but it uses fuzzy matching, which means it allows the user to type non-contiguous letters of what they’re looking for.

I wanted to set up fzf like dmenu,1 but since it’s a commandline program figuring out stdin and stdout was a challenge. I couldn’t figure out how to pipe text straight into a terminal, so I used temporary files.

The script as it stands uses the st terminal in addition to fzf. It could easily be changed to use another terminal, however.

FMENU

## bash

PROG="fmenu"
VERSION=0.0.1

version()
{
    printf '%s v%s (c) Case Duckworth' $PROG $VERSION
    exit 0
}

usage()
{
    if [ $# -ge 2 ]; then
        printf '%s: %s\n' "$PROG" "$2" >&2
    fi
    cat >&2 <<-ENDOFUSAGE
    $PROG: a dmenu clone using st and fzf
    usage:  $PROG [-g GEOMETRY] [-t NAME] [-i] [-f FZF_OPTION...]
    :   $PROG [-h] [-v]

    st(1)-tweaking options:
    -g GEOMETRY:    set terminal's geometry.
    -t NAME:    set fmenu's window title and name.
    -i:     see st(1)'s "-i" option.

    fzf(1)-tweaking options:
    -f FZF_OPTION:  add an option to fzf(1)'s command line.
    :       it should be a long option,
    :       e.g. "--exact", "--color=light", "+i".
    :       see fzf(1) for details.
    
    -h: show this help message.
    -v: display version information.

    see also st(1), fzf(1).
    ENDOFUSAGE
    exit "${1:-0}"
}

setup()
{
    IN=$(mktemp)
    OUT=$(mktemp)

    if ! ST="$(command -v st)"; then
        printf '%s: st required\n' $PROG >&2
        exit 2
    fi

    if ! FZF="$(command -v fzf)"; then
        printf '%s: fzf required\n' $PROG >&2
        exit 2
    else
        FZF="${FZF} --layout=reverse"
    fi

    GEOMETRY="60x18"
    WNAME="fmenu"
}

cleanup()
{
    rm -f "$IN" "$OUT"
    exit
}

main()
{
    cat > "$IN"

    $ST -t "$WNAME" -n "$WNAME" \
        -g "$GEOMETRY" \
        -e sh -c "${FZF} <$IN >$OUT"

    cat "$OUT"
}

trap cleanup EXIT INT

setup
while getopts ':hvg:t:f:i' opt; do
    case "$opt" in
        h) usage ;;
        v) version ;;
        g) GEOMETRY="$OPTARG" ;;
        t) WNAME="$OPTARG" ;;
        f) FZF="${FZF} $OPTARG" ;;
        # XXX: this doesn't work in dwm if there's a rule for 'fmenu'
        # windows. I tried changing the WNAME too but that didn't work.
        i) ST="${ST} -i" ;;
        \?) usage 1 "Bad option: \"-$OPTARG\"" ;;
        :) usage 1 "Argument \"-$OPTARG\" needs an argument" ;;
    esac
done
shift $((OPTIND - 1))

main "$@"
cleanup

(One of these days, I’m going to release all the scripts I’ve written in a git repo somewhere. But today is not that day.)

Now that I think about it, I should also include options to make fmenu a drop-in dmenu replacement. But that, too, is for another day.


  1. There is a patch that adds fuzzy searching for dmenu, but I didn’t like how it worked. I also wanted this challenge, of course.↩︎