A TARDIS for Your Shell: Mind Reading Teleportation through Space and Time

Introduction

What if I told you that you could install a mind-reading teleportation system in your shell that got you to where you want to go without you need to think (too much) about it?

What if I told you that as you use it more and more, it learns about you and your habits and gets more and more right to the point where it spookily seems to be able to read your mind?

What if I told you that, not only could you it get you anywhere in (filesystem) space, it could also take you anywhere in (command) history?

What if I told you that, with a few tiny, highly portable programs installed, and a few lines in your configuration file, you could basically install a TARDIS for your shell on your command-line just a few keystrokes away?

What if I told you that all this combined has an unanticipated massive synergestic effect on your productivity? The seemingly tiny savings in navigating the shell adds up into significant time savings. But far more important is because your mind does not need to take a jarring break from what you already doing to switch its processing program banks from “work” to “(shell) housekeeping” to recall the directory, filename, etc. that you need, your primary productivity increases AND your background distraction/frustation levels decrease, and all this combines into one big positive feedback loop that keep you happier and less busy while doing more?

Interested?

Read on …

Ctrl-R“: “reverse-search-history

First let’s talk about what we already have in your native, stock shell, Ctrl-R. This is, by default, bound to “reverse-search-history“: typing this results in a prompt, and as you type characters in, commands in your command history filtered for those characters show up. When you see a command you like, hit “ENTER”, and your command line gets populated with the command, ready for editing (either directly or in an external editor by triggering the “edit-and-execute-command” function, if you have this mapped). It really is very nifty. If you were not already familiar with “Ctrl-R”, I suggest you stop reading this, and spend the next few days working with it. You will find it transformative, and it may even be enough for you for a long time. I have been using it for years, and it really is a major ergonomic boost.

BUT it does have its limitations: (a) its search/filtering is very limited; (b) it can only target entire commands. Not a huge deal, and definitely not a deal-breaker. What I describe below addresses these minor gaps, and I would say that the ergonomic enhancement over stock “Ctrl-R” is even greater than going from never using “Ctrl-R” to using “Ctrl-R.

From “Ctrl-R” to the Fuzzy Future

It comes down to this: a series of key shortcuts that, when entered in the shell, allow you to zip to a directory or file that you have recently accessed, as well as recall commands, with absolutely minimal disruption mental effort. The building blocks of this magic come down to a combination of two programs working together:

The first program builds up a database of your most historically accessed directories and files, and recalls these recalls these ranked by “frecency” through a single-letter command, e.g., “z” to “zoom” to a directory. This alone gets you very far, e.g., just typing “z followed by a few letters to hint at the directory that you want to get to is often enough to get you exactly where you want to go, e.g.,

[~]$ z pbg
[~/projects/phylogenetics/pleistocene-biogeography/juniper]$

However, when you live in the shell like I do, then it takes more than a few letters to sufficiently disambiguate amongst many contenders to get things right, and so to avoid the frustration of being sent to the wrong place you find yourself typing out so many characters that it often seems like you are not gaining anything over the stock shell.

This is where the second program comes in. This is one of a class of “fuzzy pipeline” selectors, that (among other things), given an input of a list of strings, presents to you a list in a little pop-up window. As you type in characters in the prompt, the list is progressively filtered through a fuzzy-matching algorithm until you make a selection and exit. Nice things happen when we combine FASD with fzf to resolve ambiguities, taking away the frustration of FASD getting things wrong or not being able to choose from the candidates.

But the real magic happens when we combine the FASD history database with fzf to autocomplete and even execute our commands. Basically, then, instead of just “<CTRL-R>” to recall our command history we get a whole suite of commands that allow use to precisely recall arbitrary complex commands and paths with a just a few keystrokes.

The full set of commands are:

  • <CTRL-R>g
    • Fuzzy-filter and select “frecent” directory and go to (”cd” into) it.
    • MNEMONIC: “g” for “go”.
  • <CTRL-R>w
    • Fuzzy-filter and select “frecent” file, change to its directory, and edit it.
    • MNEMONIC: “w” for “work”.
  • <CTRL-R>e
    • Fuzzy-filter and select “frecent” file and edit it (without changing its directory).
    • MNEMONIC: “e” for “edit”.
  • <CTRL-R>c
    • Fuzzy-filter and select command from history and paste into the command line.
  • <CTRL-R>f
    • Fuzzy-filter and select “frecent” file path and paste into the command line.
  • <CTRL-R>d
    • Fuzzy-filter and select “frecent” directory path and paste into the command line.

where, in each case, we have fuzzy filtering in an interactive window, and in most cases we have frecency-based ranked results.

I use <CTRL-R>g about 90% of the time, and <CTRL-R>w the remainder of the time. (Frankly, I forgot about the other commands till I looked into my ~/.bashrc to write this article!) Here is a small movie of <CTRL-R>g in action:

Setup

  1. Install FASD as per the instructions.

  2. Install JUST the binary for fzf, NOT all the various shell bindings, shortcuts, functions and keystrokes; we will be overriding that ourselves.

  3. Add the following to your “~/.bashrc” (or, better yet, save the following in a separate file and source it into your “.bashrc”):

function __select_and_print_frecent_filepath__() {
    local fullpath
    fullpath="$(fasd -Rfl "$1" | fzf -1 --no-sort +m --inline-info)" \
        && echo ${fullpath} || echo ""
}

function __select_and_print_frecent_directory__() {
    local dir
    dir="$(fasd -Rdl "$1" | fzf -1 --no-sort --inline-info)" && echo "${dir}"
}

function __select_and_print_frecent_directory_cd__() {
    local dir
    dir="$(fasd -Rdl "$1" | fzf -1 --no-sort --inline-info)" && echo "cd ${dir}"
}

function __select_and_print_frecent_file_edit__() {
    local fullpath
    fullpath="$(fasd -Rfl "$1" | fzf -1 --no-sort --inline-info)" \
        && echo "vim ${fullpath}" || echo ""
}

function __select_and_print_frecent_file_cd_and_edit__() {
    local fullpath
    fullpath="$(fasd -Rfl "$1" | fzf -1 --no-sort --inline-info)" \
        && echo "cd $(dirname ${fullpath}) && vim $(basename ${fullpath})" || echo ""
}
function __select_and_print_history__() {
    local line
    line=$(tail -n10000 $HOME/.bash_history \
            | grep -v '^#' \
            | fzf -1 --no-sort --inline-info)
    echo $line
}

set -o vi # Placed here so that vi key bindings are correctly mapped below
# Remove the default <CTRL-R> 'reverse-i-search' mapping
bind -r '\C-r'
if [[ ! -o vi ]]; then
    # Required to refresh the prompt after fzf
    bind '"\er": redraw-current-line'
    bind '"\e^": history-expand-line'

    # <ctrl-R>f - paste the selected MRU file path into the command line
    bind '"\C-rf": " \C-u \C-a\C-k`__select_and_print_frecent_filepath__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
    # <CTRL-R>d - Paste the selected MRU directory path into the command line
    bind '"\C-rd": " \C-u \C-a\C-k`__select_and_print_frecent_directory__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
    # <CTRL-R>g - 'go to' (cd into) the selected MRU directory
    bind '"\C-rg": " \C-e\C-u`__select_and_print_frecent_directory_cd__`\e\C-e\er\C-m"'
    # <CTRL-R>e - Edit the selected MRU file
    bind '"\C-re": " \C-e\C-u`__select_and_print_frecent_file_edit__`\e\C-e\er\C-m"'
    # <CTRL-R>w - ('w' for 'work') change to directory of selected MRU file and edit it
    bind '"\C-rw": " \C-e\C-u`__select_and_print_frecent_file_cd_and_edit__`\e\C-e\er\C-m"'
    # <CTRL-R>c - Paste the selected command from history into the command line
    bind '"\C-rc": " \C-e\C-u\C-y\ey\C-u`__select_and_print_history__`\e\C-e\er\e^"'

    # <CTRL-T>f - paste the selected filepath into the command line
    bind '"\C-tf": " \C-u \C-a\C-k`__select_and_print_discovered_filepath__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
    # <CTRL-T>d - Paste the selected MRU directory path into the command line
    bind '"\C-td": " \C-u \C-a\C-k`__select_and_print_discovered_directory__`\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
    # <CTRL-T>g - 'go to' (cd into) the selected directory
    bind '"\C-tg": " \C-e\C-u`__select_and_print_discovered_directory_cd__`\e\C-e\er\C-m"'
    # <CTRL-T>e - Edit the selected MRU file
    bind '"\C-te": " \C-e\C-u`__select_and_print_discovered_file_edit__`\e\C-e\er\C-m"'
    # <CTRL-T>w - ('w' for 'work') change to directory of selected MRU file and edit it
    bind '"\C-tw": " \C-e\C-u`__select_and_print_discovered_file_cd_and_edit__`\e\C-e\er\C-m"'

else
    # We'd usually use "\e" to enter vi-movement-mode so we can do our magic,
    # but this incurs a very noticeable delay of a half second or so,
    # because many other commands start with "\e".
    # Instead, we bind an unused key, "\C-x\C-a",
    # to also enter vi-movement-mode,
    # and then use that thereafter.
    # (We imagine that "\C-x\C-a" is relatively unlikely to be in use.)
    bind '"\C-x\C-a": vi-movement-mode'

    bind '"\C-x\C-e": shell-expand-line'
    bind '"\C-x\C-r": redraw-current-line'
    bind '"\C-x^": history-expand-line'

    # <CTRL-R>f - Paste the selected MRU file path into the command line
    bind '"\C-rf": "\C-x\C-a$a \C-x\C-addi`__select_and_print_frecent_filepath__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
    bind -m vi-command '"\C-rf": "i\C-rf"'
    # <CTRL-R>d - Paste the selected MRU directory path into the command line
    bind '"\C-rd": "\C-x\C-a$a \C-x\C-addi`__select_and_print_frecent_directory__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
    bind -m vi-command '"\C-rd": "i\C-rd"'
    # <CTRL-R>g - 'go to' (cd into) the selected MRU directory
    bind '"\C-rg": "\C-x\C-addi`__select_and_print_frecent_directory_cd__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-rg": "ddi`__select_and_print_frecent_directory_cd__`\C-x\C-e\C-x\C-r\C-m"'
    # <CTRL-R>e - Edit the selected MRU file
    bind '"\C-re": "\C-x\C-addi`__select_and_print_frecent_file_edit__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-re": "ddi`__select_and_print_frecent_file_edit__`\C-x\C-e\C-x\C-r\C-m"'
    # <CTRL-R>w - ('w' for 'work') change to directory of selected MRU file and edit it
    bind '"\C-rw": "\C-x\C-addi`__select_and_print_frecent_file_cd_and_edit__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-rw": "ddi`__select_and_print_frecent_file_cd_and_edit__`\C-x\C-e\C-x\C-r\C-m"'
    # <CTRL-R>c - Paste the selected command from history into the command line
    bind '"\C-rc": "\C-x\C-addi`__select_and_print_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
    bind -m vi-command '"\C-rc": "i\C-rc"'

    # <CTRL-T>f - Paste the selected filepath into the command line
    bind '"\C-tf": "\C-x\C-a$a \C-x\C-addi`__select_and_print_discovered_filepath__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
    bind -m vi-command '"\C-tf": "i\C-tf"'
    # <CTRL-T>d - Paste the selected MRU directory path into the command line
    bind '"\C-td": "\C-x\C-a$a \C-x\C-addi`__select_and_print_discovered_directory__`\C-x\C-e\C-x\C-a0Px$a \C-x\C-r\C-x\C-axa "'
    bind -m vi-command '"\C-td": "i\C-td"'
    # <CTRL-T>g - 'go to' (cd into) the selected directory
    bind '"\C-tg": "\C-x\C-addi`__select_and_print_discovered_directory_cd__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-tg": "ddi`__select_and_print_discovered_directory_cd__`\C-x\C-e\C-x\C-r\C-m"'
    # <CTRL-T>e - Edit the selected file
    bind '"\C-te": "\C-x\C-addi`__select_and_print_discovered_file_edit__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-te": "ddi`__select_and_print_discovered_file_edit__`\C-x\C-e\C-x\C-r\C-m"'
    # <CTRL-T>w - ('w' for 'work') change to directory of selected file and edit it
    bind '"\C-tw": "\C-x\C-addi`__select_and_print_discovered_file_cd_and_edit__`\C-x\C-e\C-x\C-r\C-m"'
    bind -m vi-command '"\C-tw": "ddi`__select_and_print_discovered_file_cd_and_edit__`\C-x\C-e\C-x\C-r\C-m"'

fi

Note that, in all the above, where an editor is needed, I’ve used vim as the edit command. You can subsitute your own — e.g., gvim or emacs (said with a straight face). Or you can use the environmental variable $EDITOR, which is set to your default.

Share