The terminal as a cockpit
As developers, we’re constantly seeking ways to improve our efficiency and workflow. While
GUI tools have their place, there’s something uniquely powerful about mastering the terminal.
By placing the terminal at the center of our development workflow, we unlock a world of flexibility,
speed, and automation that graphical user interfaces simply can’t match.
This post is about how one can set up the terminal as a highly customizable, powerful IDE that makes
you never want to use the mouse again.
Everything in this post relates to my own personal configuration files/dotfiles.
Unix-based Environment
The Unix philosophy in action
The foundation of an effective terminal-centered workflow starts with a Unix-based host system. It doesn’t matter if you’re on macOS, Linux, or using WSL. The important thing is to be able to script in bash and have Unix/Linux binaries available.
The Unix philosophy states that all programs should be small in scope and composable with others. This gives us an incredible base for automations due to the scripting capabilities of Unix environments.
For example, we could automate the killing of a locally running webserver that gets stuck sometimes like so:
kill $(ss -lptn | grep 5173 | grep -o "pid=[0-9]*" | cut -d "=" -f2)
First we use ss -lptn
to get a list of all processes including the process ids (PIDs) and the
socket they are listening on. Then we use grep
to extract the line with the port that our webserver
is using. Afterwards we use grep
again and cut
to further shrink the string and finally extract
the PID. The result is fed into kill
, which shuts down the process.
You might think that coming up with such seemingly cryptic commands is not worth the effort, but as soon as you get to know some unix tools you’ll be able to to come up with these one-liners in a matter of minutes - and if you wrote it once, you have it in our repertoire forever.
The superiority of Unix in this space is confirmed by the sheer amount of Open Source CLI programs on Github, which are only available for Unix based systems. The truth is that Microsoft jumped too late on the bandwagon of good CLIs and people have never really caught on to develop such programs for Windows to a meaningful capacity (that’s probably one of the reasons why WSL was developed).
Arch Linux WSL
My WSL distribution of choice is the Arch Linux WSL by yuk7. While Arch Linux is notoriously difficult to set up directly, with its WSL version we just have to set up our user and are ready to go. There are two key factors that make me favor Arch Linux above all other distros:
- It comes as a rolling release. As soon as a new version of a program is published, I can get it without having to upgrade my entire system like it is the case e.g. with Debian
- The AUR (Arch User Repository) is a community-driven package registry that contains every tool you could wish for. In fact, I never had to install a software manually on Arch (except for internal tools)
Who would not like to be able to install everything they need with a single command and always have the most up-to-date version?
Terminal Multiplexer
When starting our Terminal emulator of choice, by default we end up with a single window containing a single shell instance. A lot of terminal emulators have support for tabs, some even support panes, so that we can have multiple shell windows side by side.
CLI tools that provide the same tab and pane functionality inside a single terminal window are called Terminal Multiplexers. The most prominent examples include tmux and my personal favorite zellij.
You might be thinking why use a terminal multiplexer and not just use the tab functionality of your terminal application. The answer is that terminal multiplexers are considerably more customizable and they are not dependent on the terminal application.
The functionality of both zellij
and tmux
goes far beyond simple pane and window management. Among many other features, they
allow for detaching and reattaching to/from sessions, to put the entire shell scrollback into an editor
and save and load window layouts.
zellij
stands out for its user-friendliness, nice UI and a comprehensive documentation, which
makes it my personal tool of choice. Even though
it has not been around for as long as tmux, I have yet to find a usecase that zellij
doesn’t
cover.
When using programs in the terminal it is crucial to know the keybindings of the applications and
zellij
is no different. At the very top, our tabs are displayed. At the bottom, zellij presents
us a statusline, which acts as a nice cheat sheet for the keybindings. For example, by pressing alt+t
,
we can enter tab mode. Then the statusbar updates to show us the new actions we can perform in tab mode:
This works the same for any of the other modes, which makes it very easy to learn zellij
.
Just as easy is creating a configuration file with all the default keybindings included. One simply has to
issue this command:
zellij setup --dump-config > ~/.config/zellij/config.kdl
Personally I change tabs very often, that’s why I bind alt+a
,alt+s
,alt+d
and alt+f
to quickly open
tabs 1-4. The methodology that works best for me is to use alt
bindings for my terminal multiplexer and
some useful OS bindings like switching virtual desktops, while keeping Ctrl
free for programs running
inside the terminal.
You can take a look at my zellij config here.
Bash
More than a REPL
Bash (the Bourne Again SHell) has been the default shell on most Unix-based systems for decades. No matter which Unix system you are on, you can always run bash. By learning bash, we gain the ability to script on Windows (WSL), Linux and MacOS, which is an absolutely incredible capability to have. Aside from desktop systems, server side and cloud infrastructure developers also gain great benefit through the automation capabilities of bash. After all, most of the systems in this space run on Linux or are at least managed by tools running on Unix systems.
If we only thought about a shell as a REPL, we would do it no justice. We can think of our shell as the kernel
of the terminal that we use - a kernel that can do a lot more than just call programs and show their output.
For example we can create any custom keybinding we like. This includes the customization of built-in functionality like clearing
the screen (default Ctrl-l
), but also the invocation of any external program like so:
bind -x '"C-g":"clear; git status"'
With this configuration, pressing Ctrl-g
clears the screen and then executes git status
.
Keybindings should be reserved
for things that are needed really, really often. If one does not live up to this rule, it will become a hassle to find
available keybindings at some point. For this reason I only I have 2 bash keybindings in my bashrc:
bind "C-p":previous-history # Selects the previous entry from histroy
bind "C-n":next-history # Selects the next entry from history
To still get things done with only a few keystrokes, we can use aliases instead. Aliases act as personal shortcuts, transforming lengthy commands into (muscle-)memorable abbreviations that save precious keystrokes. Functions take this concept further, allowing us to define reusable functionality while also having the freedom to define the code on multiple lines and use input arguments. These are my personal favorite aliases:
# quick ls
alias l='ls -l'
alias la='ls -a'
alias lla='ls -la'
alias lt='ls --tree'
# quick git
alias gs='git status -u'
alias gd='git diff'
alias gdc='git diff --cached'
alias gdb='git diff development --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space --find-renames'
alias gcm='git commit -m'
alias gl="git log --graph --pretty=format:'%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s' --date=short"
alias gaa='git add "*"'
alias gfa='git fetch --all'
alias gp='git pull'
For those seeking even more features, alternatives like Zsh or Fish offer enhanced functionality while maintaining most bash compatibility, but I personally just stick to bash because it covers all of my use cases and I enjoy not having to thing about compatability. By installing oh-my-bash, we can obtain a very nice looking prompt that even displays meta information like the current git branch we are on:
For fish and zsh, there is also a plentitude of shell plugins available.
The smallest plugin system in the world
Another thing that I have configured in bash is what I call the smallest plugin system in the world and the entire
magic is at the very top of my bashrc
:
shopt -s nullglob
for filename in ~/.config/local_bash_plugins/*; do
source $filename
done
shopt -u nullglob
First we set the nullglob option. By default when globs without a match are expanded in bash,
the resulting string is the glob itself (which would make script execution fail if there were no plugins).
The actual magic is just the source
call to every file inside /.config/local_bash_plugins/*
.
Inside this directory we can put all the files we want bash to load on startup. This allows us to
- group functionality we only need on one machine without having to commit the changes to our dotfiles repo
- create project-specific configurations, which we do not want to make public
- enable/disable certain plugins by putting a
return
into the first line of the plugin
Fuzzy searching anything with fzf
fzf is a general purpose fuzzy filtering program. For example it can be used to
- filter git commits
- search for a database to connect to
- search for a process
fzf
is worth a post of its own, but I will show you the two most important of its commands, which I also
registered as bash keybindings (because they are so good).
If we install fzf
as a system package, we usually also get its shell keybindings script installed.
If we source that file like so: source /usr/share/fzf/key-bindings.bash
, we gain two incredible useful keybindings:
Ctrl-R
, which fuzzy searches through the command historyCtrl-T
, which fuzzy searches directories
Both of these keybindings save me from a lot of acrobatics daily.
Dotfiles Management
As you customize your terminal environment, you’ll accumulate configuration files (dotfiles) for your shell, multiplexer, editor, and other tools. Backing these up becomes important, especially when working across multiple machines. What many people do is just create a GitHub repository that holds their dotfiles, just like the one I linked in the intro section.
In my personal dotfiles I use setup scripts to easily install my entire configuration on a new system within seconds,
you can see how it works here.
The way this works is quite simple. For each file in the repository, I define a target location on the system.
Then the installation script loops through all files and creates a symlink to the target destination of every file.
Not only does this mean that all the configuration is available in the right places, but this also means that i have a
central place where I can update my configuration. I can just push/pull changes, and I have performed an update
of all of my local dotfiles.
Using this method is of course not only limited to programs running in the terminal. My dotfiles also include my .ideavimc
for the ideavim IntelliJ plugin and my windows terminal settings.
Conclusion
Embracing the terminal as the center of your development workflow is not about looking like a hacker.
It’s about leveraging decades of tool development and Unix philosophy to create a powerful, scriptable environment.
Start small by incorporating one tool at a time into your workflow. Learn the basics of a terminal multiplexer, get comfortable
with bash, and gradually add text manipulation tools like grep, find, and tr. As you build confidence, consider moving more
of your editing to Neovim or another terminal-based editor.
Trading a sophisticated IDE for a text editor is not necessary though. In the end, whether you like terminal based text editors
or not comes down to your personal taste and needs.
The skills you develop will transfer across machines and operating systems and will serve you for the upcoming decades of your carreer.