Forest of UNIX

Supercharging your terminal with fzf

I love using the terminal, it allows me to quickly access a large amount of tools to tackle any problem I might face. But I tell you what I do not like, unnecessary typing. Way too often I encounter the scenario where I need to go from one directory to another directory somewhere far away on my system.

# I would rather chew moist chalk, than waste time having to manually cd into this directory 9 times a day
cd some/directory/i/need/to/be/that/is/suffering/to/manually/type

Luckily there is a nifty little tool called fzf. Its a command line fuzzy finder. Here is a little demo of me using fzf to print a file using batcat:

FZF demo

We could use this to get the path to any file on the system. We could then use the rifle program from ranger to then open this file using the correct program for its mimetype:

FZF rifle

Rifle decides that the .cpp file is best opened using neovim. We can also use fzf to cd to a directory:

cd "$(dirname "$(fzf)")"

And this works splendidly. But we would not want to have to retype this command everytime we want to fzf into a directory or use rifle to open a file. So lets define them as functions, in our .zshrc or .bashrc:

# Use rifle to open programs
function z_fzf {
  rifle "$(fzf)"
}

# cd into any directory using fzf
function z_fzfdir {
  cd "$(dirname "$(fzf)")"
}

Now if we source our .zshrc we will have instant access to these functions in our terminal. Except we can do better! It would be nice if we could bind our z_fzfdir and z_fzf functions under Alt+S and Alt+Shift+S.

As you might know shells have keybinds. These keybinds can actually be rebound using bindkey in Zsh. You can get a list of all bound keybinds by running bindkey.

[hackerman@droid (0) ~]
$> bindkey | head
"^@" set-mark-command
"^A" beginning-of-line
"^B" backward-char
"^D" delete-char-or-list
"^E" end-of-line
"^F" forward-char
"^G" send-break
"^H" backward-delete-char
"^I" expand-or-complete
"^J" accept-line

The left column displays the keychord a command is bound to. The right column displays the command bound to the keychord.

These commands are not always programs. Most of them are Zsh line editor widgets. You can get a list of these with zle -al.

The keychords are defined by control characters.

We can use showkey -a and then type our keychord to get the keychord we want.

[hackerman@droid (0) ~]
$> showkey -a

Press any keys - Ctrl-D will terminate this program

^[s      27 0033 0x1b
        115 0163 0x73
^[S      27 0033 0x1b
         83 0123 0x53

We can then use bindkey to bind our functions to these keychords.

# The -s flag tells bindkey we want to bind to a string and not a zle command
bindkey -s "^[s" z_fzfdir
bindkey -s "^[S" z_fzf

The -s option in this case tells bind key we are binding to a string, not a Zsh line editor command. If we where to now press Alt+S right this would just insert the z_fzfdir string under where our cursor is currently located. This is not desired, ideally we would want to clear the line insert the z_fzfdir string and instantly execute it.

We can do this by inserting keychords of other bound commands into our command.

# keychord and commands we will use:
"^A" beginning-of-line
"^K" kill-line
"^M" accept-line

Now lets change our bindkey definitions.

bindkey -s "^A^K^[s^M" z_fzfdir
bindkey -s "^A^K^[S^M" z_fzf

Now if we press Alt+S the line will be cleared and z_fzfdir will be called. This is functional and it works but it is not the correct way of doing it. The correct way of doing it would be to define our z_fzfdir and z_fzf functions as a zle widget. And then use bindkey without the -s option.

If you are interested in reading more about the Zsh line editor I recommend reading the blogpost sgeb.io. You could sidestep creating your own script and use a tool like zoxide. Which also has fzf intergration. I prefer keeping my dependencies for my shell to a minimum so I dont use zoxide.