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.