Quick terminal navigation

Directory navigation is slow

Not only in the terminal but generally.

No matter if you are using the terminal or graphical file explorer - in both you are forced to do quite a lot of work to get where you want to be.

The terminal requires from you a lot of typing. Even with the TAB completion. Tho for those who mastered fast touch typing it's more convenient than using the graphical file explorer as it's easier to type a few first letters of the known directory and hit TAB than looking for the right icon with your human eyes and then aim with the mouse. The graphical file managers can be quite fast as long as the placement of the directories stays the same and your muscle memory can learn where to move the mouse - but as soon as you add or remove a directory then the layout changes and you have to go back to relying on your eyes and relearn those movement patterns. This is the advantage of the terminal because no matter how many new directories are added or removed you can just type the path you know all the same.

The first revelation

There are fuzzy searches and fancy mechanisms available in the terminals to help you navigate quickly but that's not what this post is about as I don't think those tricks solve the core of the issue.

The solution for me was to realize that there are a few main locations in the directory tree that I visit more often than the others. Those nexus locations are a convenient starting point for further navigation or are the destinations themselves that I visit often.

Having this observation the solution is simple: shortcuts - but there is more to it as after adapting them for a while I had a second and third revelation.

The key nexus locations are:

shortcut | path
---------+---------------------------
  cd     | ~ # the home directory
  cdt    | ~/tmp
  cdd    | ~/downloads
  cdp    | ~/code/project
  cdq    | /mnt/encrypted-drive/notes

I have aliases for those in my terminal and vim making it both very fast and very convenient to move around. I've been using those in various shapes and forms for years now on windows, mac and linux. That's how my configs look like:

.bashrc
alias cdt='cd ~/tmp;pwd;'
alias cdd='cd ~/downloads;pwd;'
alias cdq='cd /mnt/q;pwd;'
.vimrc
" sets local CWD for this window but opens
" new split if current buffer is saved/edited
function! OpenLocation(path_to_dir)
    if expand('%:p') != '' || &mod == 1
        if winwidth(0) >= 160
            silent! execute "vs new"
        else
            silent! execute "new"
        endif
    endif
    execute "lcd " . a:path_to_dir
    echo a:path_to_dir
    "execute "normal ^P"
    "execute "Explore " . a:path_to_dir
endfunction

noremap  cdv :call OpenLocation('/usr/share/vim/vim90/doc'):e usr_01.txt
noremap  cdt :call OpenLocation($HOME . '/tmp')
noremap  cdd :call OpenLocation($HOME . '/downloads')
noremap  cdq :call OpenLocation('/mnt/q')
noremap  cdz :call OpenLocation($HOME . '/zig//zig-linux-x86_64-0.13.0/lib/std'):e std.zig
noremap  cdc :call OpenLocation('/usr/include'):e stdio.h
Microsoft.PowerShell_profile.ps1
function cdd { (Set-Location $HOME/Downloads -PassThru).Path }
function cdv { (Set-Location $HOME/.vim -PassThru).Path }
function cdq { (Set-Location /mnt/q -PassThru).Path }
function cdt { (Set-Location $HOME/tmp -PassThru).Path }
function cdp { (Set-Location $HOME/Programming -PassThru).Path }
function cdps { (Set-Location $HOME/.config/powershell -PassThru).Path }

Note that I print out the new path after using the shortcut. For some reason it helps me feel confident in where I am.

The second revelation

Those terminal shortcuts are no different than the graphical file explorer desktop shortcuts. If you think about it the problem and the solution on both file explorers and terminals are the same:

By having a desktop shortcut icon you are solving two problems that I had with the graphical file explorers:

Both desktop shortcuts and terminal aliases also make it so that no matter where you are the same action is required to get to one of the nexus locations. Also even if the directory name or location changes the shortcuts and aliases stay the same. This is especially great when you are switching between machines and operating systems. The path to the home and project directory are different on all the systems yet the keys you have to press are always the same no matter what.

The third revelation

Having short directory names and short paths in general is almost the same as shortcuts and aliases. They are easy and fast to both type and read. Less nested directories also mean less need for navigation in general.

Imagine if you would have a very flat directory tree and very short names. For example:

    C:/Users/Name Surname/Documents/WindowsPowerShell
vs: C:/usr/name/ps

    /home/user-name/programming-projects/websites/resume/
vs: /usr/name/webcv/

These are quite extreme examples to showcase the contrast and that the problem we have with the navigation is one we create ourselves by having too big and complex nested directory tree structures and too long and descriptive names. Having shorter names does not help in the graphical file explorers but does in many other places outside of the terminal itself. But having simpler and more flat directories structure is in general better for both.

I know that on real operating systems there are standards and conventions which do not make it practical to start changing all the paths to the shorter version. So the general advice is to make aliases and shortcuts for those nexus locations like home directory and keep the structure short and simple up from there.

But you do have control over the directory structure in your projects. It amazes me how complex and long paths you can find in simple React and Java projects. To add to that there is a tendency among developers to have every component and every class in a separate file within a dedicated directory which forces you to navigate around more. They will even create a directory just to put a single index.jsx file. No fuzzy search will help you in those situations.

There is also a tendency of trying to describe in great detail the directory or file instead of giving it a distinct and recognizable name.

Consider this:

project\
    documentation\
        technical-documentation\
            web-documentation\
                build\
                source\
                    components\
                        table-of-contents\
                            components\
                                table\
                                    index.jsx
                                table-item\
                                    index.jsx
                            index.jsx
                    chapters\
                        chapter-1-introduction\
                            sections-1-1\
                                index.jsx
                    index.js
                    index.html
        user-facing-documentation\ ...
    source\ ...
    README.md

vs:

proj\
    doc\
        tech\
            out\
            toc.jsx
            ch01.jsx
            index.js
            index.html
        user\ ...
    src\ ...
    readme.md

Easier to navigate, easier to manage, easier to understand, little to no navigation required and the need to switch between files is also minimized as there is just less of them.

cd summary

Make aliases and shortcuts to key locations and keep your directory structure flat and simple with short names. No special terminal tricks or GUI tools will fix the core issue behind how slow you are moving around if you have to navigate in a deep complex directory tree with long names.


# cdb - cd base

An appendix to the above article.

There is a move I do quite often but it's a more advanced technique. The task is to get back to the project base directory quickly after going deep into its directory tree:

$ tree
project\  < ----- go back here from there ------.
    .git\                                        :
    documentation\                               :
        technical-documentation\                 :
            web-documentation\                   :
                build\                           :
                source\                          :
                    components\                  :
                        table-of-contents\  > --'
                            components\
                                table\
                                    index.jsx
                                table-item\
                                    index.jsx
                            index.jsx
                    chapters\
                        chapter-1-introduction\
                            sections-1-1\
                                index.jsx
                    index.js
                    index.html
        user-facing-documentation\ ...
    source\ ...
    README.md
$ cd project/documentation/technical-documentation
$ cd web-documentation/source/components/table-of-contents
$ cd ../../../../../../..

To avoid typing cd ../../../.. with precisely calculated amount of double dots I made a cdb command which searches for project base directory and moves me there. I'm determining the project's base directory by looking for .git directory but you can use any heuristic.

.bashrc
function cdb() {
  local dir=$(pwd)
  while [ "$dir" != "/" ]
  do
    if [ -d "$dir/.git" ]
    then
      cd "$dir"
      break
    fi
    dir="$(dirname "$dir")"
  done
  pwd
}
nvim init.lua
function GetProjectBaseDir()
    local current = vim.fn.expand("%:p:h")
    local path = current
    while (true) do
      -- found project dir
      if vim.fn.isdirectory(path .. "/.git") == 1 then return path end
      local parent = vim.fn.fnamemodify(path, ':h')
      -- tried all parent directories
      if parent == path then return current end
      path = parent
    end
end
nmap ("cdb", ":e ^R=luaeval('GetProjectBaseDir()')")

And that's it for this small addition - have fun moving around.