Home

Awesome

fzf-tools.zsh

FZF-Tools is a Zsh plugin aimed to enhance your command-line workflow by providing interactive selection capabilities through fzf, allowing you to quickly find files, search & run commands from history, run scripts of many supported types, browse git commits, and more.

Table Of Contents

About

The fzf-tools plugin provides functions, key-bindings, and aliases that aim to integrate fuzzy finder capabilities into the command line as a default output for certain commands such as man, ls, find, printenv, alias and others. My aim was to make it so that fzf would work without having to manually pipe commands through it, write aliases or explicitly call functions. In other words I wanted to avoid doing the following each time:

man -k . | awk '{print $1}' | sort | uniq | fzf | xargs -r man
ls |  --color=auto | fzf
find | fzf
printenv | fzf
alias | fzf
# or...
alias l="ls --color=auto | fzf"
alias m="man -k . | awk '{print $1}' | sort | uniq | fzf | xargs -r man"

There's nothing wrong with doing any of these, but I personally feel that fzf makes a great default feature for certain commands such as ls or man. It took a lot of trial and error but I finally got everything working smoothly and functioning well. If you have suggestions, ideas etc. consult the Contacts section.

Install

To download and install fzf-tools choose an install method and follow the corresponding steps. Once fininshed jump to the Usage section.

Manual Install

  1. Download and place the zsh-toggles folder in a location of your choosing.
  2. Next source the script as shown in the Usage section.

Command Line Install

Using Git Clone:

  1. Open your terminal and navigate to the directory where you want to clone the repository:
cd where/I/want/to/install
  1. Next run the following command to clone the repository to the chosen location:
git clone https://github.com/happycod3r/fzf-tools.git

Using Curl:

  1. Pick a directory to download it to:
cd where/I/want/
  1. Paste the following line into your terminal and press the Enter (^M) key:
curl https://github.com/happycod3r/fzf-tools.git

Usage

oh-my-zsh:

To use fzf-tools with oh-my-zsh follow these 2 steps:

  1. Install fzf: You need to install fzf on your system. You can find installation instructions for your operating system in the fzf GitHub repository: https://github.com/junegunn/fzf
  2. Simply move the fzf-tools folder to the ~/.oh-my-zsh/custom/plugins directory and then add fzf-tools to the plugins array in your ~/.zshrc file.
plugins=(fzf-tools ...)

stand-alone:

To use fzf-tools without a plugin manager like oh-my-zsh follow these steps:

  1. Install fzf: You need to install fzf on your system. You can find installation instructions for your operating system in the fzf GitHub repository: https://github.com/junegunn/fzf
  2. Next put the fzf-tools folder in a place of your choosing then source the fzf-tools.zsh file in your ~/.zshrc file.
source a/dir/of/your/choosing/fzf-tools.zsh

Documentation

fzf-command-widget

Defines the 'accept-line' widget function.

Please note that the fzf-command-widget function modifies the behavior of the Enter key for specific commands, so it may not work as expected in all scenarios! Also, if you decide to add to this or change anything, be cautious when modifying the behavior of core commands like ls and man!

function  fzf-command-widget() {
	local  full_command=$BUFFER
	case  "$full_command"  in
		ls*)
			BUFFER="$full_command | fzf --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
		man*)
			BUFFER="fzf-man $full_command"
		;;
		printenv* | env*)
			BUFFER="$full_command | fzf --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
		set)
			BUFFER="$full_command | fzf --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
		grep*)
			BUFFER="$full_command | fzf -i --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
		find*)	
			BUFFER="$full_command | fzf -i --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
		'ps aux')
			BUFFER="$full_command | fzf --multi --cycle --no-sort \
				--preview='echo {}' \
				--preview-window down:10% \
				--layout='reverse-list' \
				--color bg:#222222,preview-bg:#333333"
		;;
	esac
	zle  accept-line
	# Uncomment if the long command left over on the previous prompt bothers you.
	# $(clear)
}

The fzf-command-widget function is designed to handle the behavior when the enter key is pressed. It takes the entire command line entered by the user and stores it in a variable called $full_command. The case statement then checks for different commands and prefixes the existing command with the required options, arguments, and flags, before piping it through fzf. For example, when the user enters ls -la /path/to/directory and presses Enter(^M), the ls command with options and arguments will be executed as ls --color=auto -la /path/to/directory | fzf... Similarly, other commands like ls, man, printenv (including env as an alternative), set, grep, find and ps aux will be processed with their respective options, arguments, and flags. Please keep in mind that this approach will pass the entire command line through fzf, including options, arguments, and flags. However, it does not perform in-depth parsing or validation of the command structure, so the behavior and correctness of the resulting command are dependent on the proper usage of options and arguments. Due to this, entering invalid commands may have unexpected results. If you're not sure about a command's options and flags you can always consult the man pages.

	BUFFER="$full_command | fzf --multi --cycle --no-sort \
			--preview='echo {}' \
			--preview-window right:50% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333"
	BUFFER="fzf-man $full_command"
	BUFFER="$full_command | fzf --multi --cycle --no-sort \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333"
	BUFFER="$full_command | fzf -i --multi --cycle --no-sort \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333"
	BUFFER="$full_command | fzf -i --multi --cycle --no-sort \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333"
	BUFFER="$full_command | fzf --multi --cycle --no-sort \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333"

Next we have to bind the accept-line widget function to the Enter key:

zle -N fzf-command-widget
bindkey '^M' fzf-command-widget

My original approach for detecting specific commands like ls and man involved using the precmd hook which is a function defined by zsh that gets invoked before each prompt, so essentially every time the user presses the Enter key (^M), but this wasn't straight forward enough. I found myself tinkering with code more than progressing, so I decided to just create a widget and bind it to the Enter key (^M)


fzf-man

The fzf-man function is called by the fzf-command-widget function when it detects that the user has entered the mancommand and not meant to be called externally.

function  fzf-man() {
	local  selected_command
	selected_command=$(
		man  -k . \
		|  awk '{print $1}' \
		|  sort  \
		|  uniq  \
		|  fzf  --multi  --cycle  \
			--preview='echo {}' \
			--preview-window down:10%
	)
	if [[ -n  "$selected_command" ]]; then
		man  "$selected_command"  \
			|  fzf  --multi  --cycle  --tac  --no-sort  \
				--preview='echo {}'  \
				--preview-window  down:10%  \
				--layout='reverse-list'  \
				--color  bg:#222222,preview-bg:#333333
	fi
}

fzf-run-command-from-history

Allows searching for and executing a command from your command history interactively using fzf.

function  fzf-run-cmd-from-history() {
	local  selected_command
	selected_command=$(
		history  \
		|  awk '{$1=""; print $0}' \
		|  awk '!x[$0]++' \
		|  fzf  --cycle  --tac +s --no-sort  \
			--preview 'echo {}' \
			--preview-window down:10% \
			--color bg:#222222,preview-bg:#333333 \
	)
	if [[ -n  "$selected_command" ]]; then
		eval  "$selected_command"
	fi
}

alias  fzhist='fzf-run-cmd-from-history'

Note: *Choosing a previous cd command from your history may fail to execute as the fzf-run-command-from-hisory function doesn't take into account your current position in the directory stack, so if you aren't in the same position that you were originally in when executing the cd some-dir command from your history it will fail giving you the following error: cd: no such file or directory: some-dir


fzf-exec-scripts

This command will allow you to search for a script/s within the desired directory and its subdirectories, allowing you to interactively select and execute the desired script/s with their respective interpreters. When more than one script is selected they are all executed consecutively one after the other.

function fzf-exec-scripts() {
	local directory="$1"
	shift
	local file_exts=("$@")

	if [[ -z "$directory" || "${#file_exts[@]}" -eq 0 ]]; then
	    echo "Usage: fzf-exec-scripts <directory> <file_extension1> [<file_extension2> ...]"
	    return 1
	fi

	local selected_scripts=()
	local selected_script
	selected_script=$(find "$directory" -type f \( -name "*.${file_exts[1]}" $(printf -- "-o -name '*.%s' " "${file_exts[@]:1}") \) \
		| fzf --multi -m --cycle --tac +s  \
				--preview='echo {}'  \
				--preview-window  down:10%  \
				--layout='reverse-list'  \
				--color  bg:#222222,preview-bg:#333333) && selected_scripts=("${(f)selected_script}")

	if [[ "${#selected_scripts[@]}" -eq 0 ]]; then
	    echo "No scripts selected."
	    return
	fi

	for script in "${selected_scripts[@]}"; do
	    chmod +x "$script"
	    case "$script" in
		    *.sh)
				bash  "$script"
			;;
			*.zsh)
				zsh  "$script"
			;; 
		    *.js)
		        node "$script"
	        ;;
		    *.py)
			    python "$script"
	        ;;
	        *.rb)
		        ruby "$script"
	        ;;
	        *.rs)
				filename=$(basename "${directory}/${script}")
				rustc  "$script"
				./$filename
			;;
		    *)	
		        echo "Unsupported file extension: $script"
		        return 1
	        ;;
	    esac
	done
}

alias fzscripts='fzf-exec-scripts'

To use fzf-exec-scripts supply it with the desired directory of your script/s and file extensions as parameters. When providing a file extension/s be sure to leave out the prepended '.' on the extension/s as you only need the extension name by it self (e.g. .sh -> sh | | .js -> js) . For example:

fzf-exec-scripts /path/to/scripts/ sh js py rb 

fzf-search-files-on-path

Interactively search for files on a given path.

function  fzf-search-files-on-path() {
	local  _path="$1"
	find  tree  "$_path"  -type  f  \
		|  fzf  -i --multim  --cycle  \
			--preview='echo {}'  \
			--preview-window  down:10%  \
			--color  bg:#222222,preview-bg:#333333
}

alias  fzfop='fzf-search-files-on-path'

fzf-git-log

Select a commit from git log using fzf.

function  fzf-git-log() {
	local  selected_commit
	selected_commit=$(\
		git log --oneline  |  fzf  --multi  --no-sort  --cycle  \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333 \
	) && git  show  "$selected_commit"
}

alias fzgl='fzf-git-log'

fzf-ag

Search for patterns in files using ag (The Silver Searcher) and fzf.

function  fzf-ag() {
	local  selected_file
	selected_file=$(\
		ag "$1" . |  fzf  \
			--multi  --no-sort  --cycle  \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333\
	) && $EDITOR  "$selected_file"
}

alias fzag='fzf-ag'

fzf-docker-ps

Select a Docker container from docker ps interactively using fzf.

function  fzf-docker-ps() {
	local  selected_container
	selected_container=$(docker ps -a  |  fzf  \
		--multi  --no-sort  --cycle  \
		--preview='echo {}' \
		--layout='reverse-list' \
		--color bg:#222222,preview-bg:#333333 \ 
		|  awk '{print $1}') \
		&& docker  logs  "$selected_container"
}

alias fzdps='fzf-docker-ps'

fzf-ssh

Select an SSH host from known_hosts using fzf.

function fzf-ssh() {
	local selected_host
	selected_host=$(\
		cat ~/.ssh/known_hosts \
		|  cut  -f  1  -d ' ' \
		|  sed  -e s/,.*//g |  uniq  |  fzf  --multi  --no-sort  --cycle  \
			--preview='echo {}' \
			--preview-window down:10% \
			--layout='reverse-list' \
			--color bg:#222222,preview-bg:#333333\
	) && ssh  "$selected_host"
}
alias fzssh='fzf-ssh'

fzf-grep

Interactively search for patterns in files using grep and fzf.

function  fzf-grep() {
	local  selected_file
	selected_file=$(grep  -Ril "$1" . |  fzf  --multi  --no-sort  --cycle  \
		--preview='echo {}' \
		--preview-window down:10% \
		--layout='reverse-list' \
		--color bg:#222222,preview-bg:#333333\
	) && $EDITOR  "$selected_file"
}

alias fzgrep='fzf-grep'

fzf-find

Search for files using find and fzf.

function  fzf-find() {
	local  selected_file
	selected_file=$(find . -type f |  fzf  --multi  --no-sort  --cycle  \
		--preview='echo {}' \
		--preview-window down:10% \
		--layout='reverse-list' \
		--color bg:#222222,preview-bg:#333333\
	) && $EDITOR  "$selected_file"
}

alias fzfind='fzf-find'

autoload -Uz fzf-command-widget fzf-man fzf-run-cmd-from-history fzf-exec-scripts fzf-search-files-on-path fzf-git-log fzf-ag fzf-docker-ps fzf-ssh fzf-grep fzf-find

The autoload -Uz command ensures that the functions of this plugin are lazily loaded when they are invoked.

if [[ -x  "$(command  -v fzf)" ]]; then
	export FZF_DEFAULT_COMMAND='ag -g ""'
	export FZF_DEFAULT_OPTS='-m --preview-window=up:40%:wrap'
fi

Initializes the FZF_DEFAULT_COMMAND and FZF_DEFAULT_OPTS environment variables which can be used to customize fzf's behavior. These are just some example options to start with for the 2 variables, but you can change them to whatever you would prefer.

Contributing

If you have any feature requests, suggestions or general questions you can reach me via any of the methods listed below in the "Contacts" section

Security

Reporting a vulnerability or bug?

Do not submit an issue or pull request: A general rule of thumb is to never publicly report bugs or vulnerabilities because you might inadvertently reveal it to unethical people who may use it for bad. Instead, you can email me directly at: paulmccarthy676@gmail.com. I will deal with the issue privately and submit a patch as soon as possible.

Contacts

Author: Paul M.

Back to top