nixos-config/pkgs/nix-search-fzf/nix-search-fzf.sh

162 lines
4.8 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# An fzf script with autocomplete from "nix search" which allows for interactive fuzzy searching of derivations.
# After the search a nix subcommand is executed on the selected derivation(s), e.g. "nix shell" or "nix run".
set -eou pipefail
FLAKE="nixpkgs" # The default flake to use. TODO: make this configurable
NIX_SUBCOMMAND="shell" # The default nix subcommand to execute
MULTIPLE_SELECTION=true # Whether to allow the user to select multiple derivations
PRINT_COMMAND=false # Only print the command that would be executed, don't execute it
if [ -n "${XDG_CACHE_HOME-}" ]; then
CACHE_PATH="$XDG_CACHE_HOME/nix-search-fzf/cache.txt"
else
CACHE_PATH="$HOME/.cache/nix-search-fzf/cache.txt"
fi
# Because fzf executes commands from keybindings in a subprocess, we cannot directly change this scripts state.
# Instead we can use a temporary file as an IPC mechanism, to change which subcommand to execute.
TMP_FILE="$(mktemp --dry-run --suffix "-nix-search-fzf")"
trap 'rm -f "$TMP_FILE"' EXIT INT TERM
handleArguments() {
while (("$#" > 0)); do
case "$1" in
-s | shell | --shell)
NIX_SUBCOMMAND="shell"
;;
-b | build | --build)
NIX_SUBCOMMAND="build"
;;
-r | run | --run)
NIX_SUBCOMMAND="run"
MULTIPLE_SELECTION=false
;;
-e | edit | --edit)
NIX_SUBCOMMAND="edit"
MULTIPLE_SELECTION=false
;;
-c | command | --command)
PRINT_COMMAND=true
;;
-u | update | --update)
manageCache true
exit
;;
-h | help | --help)
echo "Usage: $(basename "$0") [--shell|--build|--run|--edit|--update]"
echo " --shell: enter a nix shell with the selected package(s). This is the default"
echo " --build: build the selected package(s) with nix build"
echo " --run: run the selected package with nix run"
echo " --edit: edit the selected package with nix edit"
echo " --command: only print the command that would be executed, don't execute it"
echo " --update: update the nix search cache, this is done automatically every 10 days"
echo " --help: show this help message"
exit 0
;;
*)
echo "Unknown option '$1'"
exit 1
;;
esac
shift 1
done
}
runColored() {
printf "\e[32m\$ %s\n\e[0m" "$1"
eval "$1"
}
manageCache() {
local doUpdate="${1:-false}"
mkdir -p "$(dirname "$CACHE_PATH")"
if [ ! -f "$CACHE_PATH" ] || [ ! -s "$CACHE_PATH" ]; then
doUpdate="true"
echo "attribute path cache does not exist, generating..." >&2
elif (($(date -r "$CACHE_PATH" +%s) < $(date -d "now - 10 days" +%s))); then
doUpdate="true"
echo "cache file is older than 10 days, updating..." >&2
fi
if [ "$doUpdate" == "true" ]; then
echo "caching attribute paths..." >&2
# Create a list of all attribute paths with "legacyPackages.$arch" stripped
# In the future this could contain metadata as well, doing a "nix-eval" for each is not the fastest
nix search "$FLAKE" "^" --quiet --json | jq -r 'keys[]' | cut -d'.' -f3- >"$CACHE_PATH"
echo "successfully generated attribute path cache" >&2
fi
}
fzfBindingFlag() {
local tmpFile="$1"
local -A bindings=(
["shell"]="ctrl-s"
["build"]="ctrl-b"
["edit"]="ctrl-e"
["run"]="ctrl-r"
)
local result="--bind="
for subCommand in "${!bindings[@]}"; do
local binding="${bindings[$subCommand]}"
# When pressed, write the appropriate command to our temporary IPC file, and change the prompt accordingly
result+="$binding:execute-silent(echo $subCommand > $tmpFile)+change-prompt($subCommand > ),"
done
echo "${result%,}"
}
runFzf() {
local multi_flag
if [ "$MULTIPLE_SELECTION" == true ]; then
multi_flag="--multi"
else
multi_flag="--no-multi"
fi
fzf "$multi_flag" \
--height 40% \
--preview-window right,70% \
--border rounded \
--prompt "$NIX_SUBCOMMAND > " \
--preview "bash -c \"@previewText@ {} $FLAKE\"" \
"$(fzfBindingFlag "$TMP_FILE")" <"$CACHE_PATH"
}
runNix() {
local packages selectedPkgs command
readarray -t selectedPkgs <<<"$@"
((${#selectedPkgs[@]} == 0)) && exit 0
if [ "$MULTIPLE_SELECTION" == true ] && ((${#selectedPkgs[@]} > 1)); then
# Build a brace expansion string
local pkg_list="{"
for pkg in "${selectedPkgs[@]}"; do
pkg_list+="$pkg,"
done
packages="${pkg_list%,}}"
else
packages="${selectedPkgs[0]}"
fi
((${#packages} == 0)) && exit 0
# Update what subcommand to execute, in case it was changed by a keybinding from fzf
[ -s "$TMP_FILE" ] && NIX_SUBCOMMAND="$(<"$TMP_FILE")"
command="NIXPKGS_ALLOW_UNFREE=1 nix $NIX_SUBCOMMAND $FLAKE#$packages --impure"
if [ "$PRINT_COMMAND" == true ]; then
echo "$command"
exit 0
else
runColored "$command"
fi
}
handleArguments "$@"
manageCache
runNix "$(runFzf)"