#!/usr/bin/env bash set -u version=0.57.0 auto_completion= key_bindings= update_config=2 shells="bash zsh fish" prefix='~/.fzf' prefix_expand=~/.fzf fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish help() { cat << EOF usage: $0 [OPTIONS] --help Show this message --bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh} --all Download fzf binary and update configuration files to enable key bindings and fuzzy completion --xdg Generate files under \$XDG_CONFIG_HOME/fzf --[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C) --[no-]completion Enable/disable fuzzy completion (bash & zsh) --[no-]update-rc Whether or not to update shell configuration files --no-bash Do not set up bash configuration --no-zsh Do not set up zsh configuration --no-fish Do not set up fish configuration EOF } for opt in "$@"; do case $opt in --help) help exit 0 ;; --all) auto_completion=1 key_bindings=1 update_config=1 ;; --xdg) prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf' prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf" ;; --key-bindings) key_bindings=1 ;; --no-key-bindings) key_bindings=0 ;; --completion) auto_completion=1 ;; --no-completion) auto_completion=0 ;; --update-rc) update_config=1 ;; --no-update-rc) update_config=0 ;; --bin) ;; --no-bash) shells=${shells/bash/} ;; --no-zsh) shells=${shells/zsh/} ;; --no-fish) shells=${shells/fish/} ;; *) echo "unknown option: $opt" help exit 1 ;; esac done cd "$(dirname "${BASH_SOURCE[0]}")" fzf_base=$(pwd) fzf_base_esc=$(printf %q "$fzf_base") ask() { while true; do read -p "$1 ([y]/n) " -r REPLY=${REPLY:-"y"} if [[ $REPLY =~ ^[Yy]$ ]]; then return 1 elif [[ $REPLY =~ ^[Nn]$ ]]; then return 0 fi done } check_binary() { echo -n " - Checking fzf executable ... " local output output=$("$fzf_base"/bin/fzf --version 2>&1) if [ $? -ne 0 ]; then echo "Error: $output" binary_error="Invalid binary" else output=${output/ */} if [ "$version" != "$output" ]; then echo "$output != $version" binary_error="Invalid version" else echo "$output" binary_error="" return 0 fi fi rm -f "$fzf_base"/bin/fzf return 1 } link_fzf_in_path() { if which_fzf="$(command -v fzf)"; then echo " - Found in \$PATH" echo " - Creating symlink: bin/fzf -> $which_fzf" (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf) check_binary && return fi return 1 } try_curl() { command -v curl > /dev/null && if [[ $1 =~ tar.gz$ ]]; then curl -fL $1 | tar --no-same-owner -xzf - else local temp=${TMPDIR:-/tmp}/fzf.zip curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp" fi } try_wget() { command -v wget > /dev/null && if [[ $1 =~ tar.gz$ ]]; then wget -O - $1 | tar --no-same-owner -xzf - else local temp=${TMPDIR:-/tmp}/fzf.zip wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp" fi } download() { echo "Downloading bin/fzf ..." if [ -x "$fzf_base"/bin/fzf ]; then echo " - Already exists" check_binary && return fi link_fzf_in_path && return mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin if [ $? -ne 0 ]; then binary_error="Failed to create bin directory" return fi local url url=https://github.com/junegunn/fzf/releases/download/v$version/${1} set -o pipefail if ! (try_curl $url || try_wget $url); then set +o pipefail binary_error="Failed to download with curl and wget" return fi set +o pipefail if [ ! -f fzf ]; then binary_error="Failed to download ${1}" return fi chmod +x fzf && check_binary } # Try to download binary executable archi=$(uname -sm) binary_available=1 binary_error="" case "$archi" in Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;; Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;; Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;; Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;; Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;; Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;; Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;; Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;; Linux\ ppc64le) download fzf-$version-linux_ppc64le.tar.gz ;; Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;; Linux\ s390x) download fzf-$version-linux_s390x.tar.gz ;; FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;; OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;; CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;; MINGW*\ *64) download fzf-$version-windows_amd64.zip ;; MSYS*\ *64) download fzf-$version-windows_amd64.zip ;; Windows*\ *64) download fzf-$version-windows_amd64.zip ;; *) binary_available=0 binary_error=1 ;; esac cd "$fzf_base" if [ -n "$binary_error" ]; then if [ $binary_available -eq 0 ]; then echo "No prebuilt binary for $archi ..." else echo " - $binary_error !!!" fi if command -v go > /dev/null; then echo -n "Building binary (go install github.com/junegunn/fzf) ... " if [ -z "${GOPATH-}" ]; then export GOPATH="${TMPDIR:-/tmp}/fzf-gopath" mkdir -p "$GOPATH" fi if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then echo "OK" cp "$GOPATH/bin/fzf" "$fzf_base/bin/" else echo "Failed to build binary. Installation failed." exit 1 fi else echo "go executable not found. Installation failed." exit 1 fi fi [[ "$*" =~ "--bin" ]] && exit 0 for s in $shells; do if ! command -v "$s" > /dev/null; then shells=${shells/$s/} fi done if [[ ${#shells} -lt 3 ]]; then echo "No shell configuration to be updated." exit 0 fi # Auto-completion if [ -z "$auto_completion" ]; then ask "Do you want to enable fuzzy auto-completion?" auto_completion=$? fi # Key-bindings if [ -z "$key_bindings" ]; then ask "Do you want to enable key bindings?" key_bindings=$? fi echo for shell in $shells; do [[ "$shell" = fish ]] && continue src=${prefix_expand}.${shell} echo -n "Generate $src ... " fzf_completion="source \"$fzf_base/shell/completion.${shell}\"" if [ $auto_completion -eq 0 ]; then fzf_completion="# $fzf_completion" fi fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\"" if [ $key_bindings -eq 0 ]; then fzf_key_bindings="# $fzf_key_bindings" fi cat > "$src" << EOF # Setup fzf # --------- if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then PATH="\${PATH:+\${PATH}:}$fzf_base/bin" fi EOF if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then if [[ "$shell" = zsh ]]; then echo "source <(fzf --$shell)" >> "$src" else echo "eval \"\$(fzf --$shell)\"" >> "$src" fi else cat >> "$src" << EOF # Auto-completion # --------------- $fzf_completion # Key bindings # ------------ $fzf_key_bindings EOF fi echo "OK" done # fish if [[ "$shells" =~ fish ]]; then echo -n "Update fish_user_paths ... " fish << EOF echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin EOF [ $? -eq 0 ] && echo "OK" || echo "Failed" fi append_line() { local update line file pat lines update="$1" line="$2" file="$3" pat="${4:-}" lines="" echo "Update $file:" echo " - $line" if [ -f "$file" ]; then if [ $# -lt 4 ]; then lines=$(\grep -nF "$line" "$file") else lines=$(\grep -nF "$pat" "$file") fi fi if [ -n "$lines" ]; then echo " - Already exists:" sed 's/^/ Line /' <<< "$lines" update=0 if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then echo " - But they all seem to be commented" ask " - Continue modifying $file?" update=$? fi fi set -e if [ "$update" -eq 1 ]; then [ -f "$file" ] && echo >> "$file" echo "$line" >> "$file" echo " + Added" else echo " ~ Skipped" fi echo set +e } create_file() { local file="$1" shift echo "Create $file:" for line in "$@"; do echo " $line" echo "$line" >> "$file" done echo } if [ $update_config -eq 2 ]; then echo ask "Do you want to update your shell configuration files?" update_config=$? fi echo for shell in $shells; do [[ "$shell" = fish ]] && continue [ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}" done if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then bind_file="${fish_dir}/functions/fish_user_key_bindings.fish" if [ ! -e "$bind_file" ]; then mkdir -p "${fish_dir}/functions" create_file "$bind_file" \ 'function fish_user_key_bindings' \ ' fzf --fish | source' \ 'end' else echo "Check $bind_file:" lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ') if [[ -n $lno ]]; then echo " ** Found 'fzf_key_bindings' in line #$lno" echo " ** You have to replace the line to 'fzf --fish | source'" echo else echo " - Clear" echo append_line $update_config "fzf --fish | source" "$bind_file" fi fi fi if [ $update_config -eq 1 ]; then echo 'Finished. Restart your shell or reload config file.' if [[ "$shells" =~ bash ]]; then echo -n ' source ~/.bashrc # bash' [[ "$archi" =~ Darwin ]] && echo -n ' (.bashrc should be loaded from .bash_profile)' echo fi [[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh" [[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish' echo echo 'Use uninstall script to remove fzf.' echo fi echo 'For more information, see: https://github.com/junegunn/fzf'