Supercharging asdf with FZF

Update (August 12, 2022): Just noticed this commit that changes the output format for the current version in asdf list — updated accordingly.

Update (August 11, 2022): I’ve added more intelligent version sorting and support for asdf’s “latest” alias in both commands.

I’ve switched to asdf for managing all my language versions. It seems to work well, but we can make it work better by adding fzf.

Installing New Versions

The syntax for installing a new tool version in asdf is:

$ asdf install <name> <version>

Where <name> is the name of the tool (e.g., “golang”, “java”), and <version> is the version (e.g., “1.18”, “corretto-18.0.0.37.1”). Example:

$ asdf install java corretto-18.0.0.37.1

We can use fzf for both name and version parameters. We’ll start with name.

Selecting the Tool Name

asdf implements each tool via a plugin. To get the list of installed plugins, type:

$ asdf plugin list

So in our function, we’ll accept the tool name via a parameter, and if not specified, we’ll pipe the plugin list to fzf. So the beginning of our function looks like this:

# Install a new tool version using asdf and fzf
ai() {
  local name
  if [[ $# -eq 0 ]]; then
    name=$(asdf plugin list | fzf)
  else
    name=$1
  fi
}

Whether we type ai golang or just ai and pick golang from the fzf list, we now have “golang” in our name variable.

The next part gets a little tricky, since we want to list only versions we haven’t already installed. This command lists all versions for a given name:

$ asdf list all <name>

And this version lists the versions we have installed:

$ asdf list <name>

So we have to subtract the lists. The comm command will help us. Here’s the first part of its man page:

COMM(1)                            User Commands                            COMM(1)

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

DESCRIPTION
       Compare sorted files FILE1 and FILE2 line by line.

       When FILE1 or FILE2 (not both) is -, read standard input.

       With  no  options,  produce  three-column output.  Column one contains lines
       unique to FILE1, column two contains lines unique to FILE2, and column three
       contains lines common to both files.

       -1     suppress column 1 (lines unique to FILE1)

       -2     suppress column 2 (lines unique to FILE2)

       -3     suppress column 3 (lines that appear in both files)

So want to pass:

  • The list of all versions, sorted by version
  • The list of installed versions, sorted by version
  • The flag to suppress column 2 (our installed versions that aren’t in all versions — should be empty, but just in case)
  • The flag to suppress column 3 (our installed versions)

We pass the --version-sort flag to sort to sort by version, not alphabetically. The man page for sort says:

-V, --version-sort
       natural sort of (version) numbers within text

The output for installed versions has leading whitespace, which we want to strip else they won’t match, so the command to list only versions we haven’t installed is:

$ comm -23 <(asdf list all $name | sort --version-sort) <(asdf list $name | awk '{print $1}' | sort --version-sort)

One last thing: we also want to make asdf’s “latest” alias available (to install the latest version), so we add it to the list of versions. To put it all together, we pipe that list to fzf, and pass the selection as the second parameter to asdf install. Our function looks like this:

# Install a new tool version using asdf and fzf
ai() {
  local name
  if [[ $# -eq 0 ]]; then
    name=$(asdf plugin list | fzf)
  else
    name=$1
  fi
  asdf install $name $({ comm -23 <(asdf list all $name | sort --version-sort) <(asdf list $name | awk '{print $1}' | sort --version-sort); echo "latest"; } | fzf)
}

Switching Versions

Setting the global version of a tool looks like this:

$ asdf global <name> <version>

Similar. We’ll reuse the name field (parameter or fzf selection) in our function to switch our current version of a tool.

We already know how to list our installed versions of a tool:

$ asdf list <name> | awk '{print $1}'

So we could just pass that for the second parameter, but then we’d include the currently selected version as well. The current version is marked by a leading asterisk. We can use grep -v to remove the current version from the list.

We also want to add the “latest” alias to our list.

Our final function looks like this:

# Switch to a new tool version using asdf and fzf
au() {
  local name
  if [[ $# -eq 0 ]]; then
    name=$(asdf plugin list | fzf)
  else
    name=$1
  fi
  local current=$(asdf current $name | awk '{print $2}')
  asdf global $name $({asdf list $name | awk '{print $1}' | grep -v "^\*"; echo "latest"; } | fzf)
}

Golden.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.