Upgrading your dev environment is the quickest win to be more productive. A trustworthy environment is like shopping in your nearby supermarket for the hundredth time. You know exactly where to find what you need. Everything feels familiar and diligently arranged. On the contrary, an unreliable environment feels like being lost in the endless alleys of a crowded souk where each shopkeeper screams for your attention.
Improving a dev environment is an exploratory process. Start from a blank file, then tour all blog posts and GitHub repos like a curious traveler anxious to miss any hidden gem. Ask your friends and colleagues for recommendations. They will proudly share their favorite Bash scripts and browser extensions. Yet exploring doesn't mean strolling without direction, so prioritize. Track down your most irritating config bugs [1]. Identify your most frequent boring actions then hunt the tool to automate them. If you fall down a rabbit hole, settle on a first version of your config. It won't be perfect, but will deliver value immediately.
Document all instructions to reproduce your environment from scratch meticulously. There is no room for doubt at any step. Even dragging a slider in a Preferences window must be described. Store the instructions in a Git repo along with all configurations. You might realize later that you forgot some steps. When they cross your mind, note them down immediately and make a point to update your repo by end of day. Technical debt is real for dev environments too. If you lack rigor, you're up for guaranteed frustration.
Like a shopping list, this dev environment is yours. No general-purpose config from the Internet will ever meet all your expectations. Focus on your own unique needs. If you can't memorize some allegedly cool alias, ditch it without regret then craft your own. Complicated solutions are oftentimes rigid, so prefer widely adopted solutions that you understand clearly. Keep it simple and embrace minimal configuration.
Your dev environment is your ideal supermarket. Its aisles feel familiar. They prominently display the products you love. You can stick to your shopping list and pick up that spicy barbecue sauce that only you can eat. You can breeze to the cashier, or maybe check out the latest trends. But whatever you decide, you always come back home swiftly with everything you need to cook a delicious meal.
[1] With my Swiss keyboard, the backspace key in the micro
editor was duplicating lines instead of forward-deleting. Typing was awful.
I use a Mac Book Pro at work and a desktop computer with Ubuntu 20.04 at home. However, most of the terminal tools I use are common to both operating systems.
I use ZSH with ZSH-completions, as well as Oh My ZSH. The oh-my-zsh wiki is worth reading to learn about commonly used aliases (e.g. ...
=> cd ../..
) and optional plugins that might be of interest to you. If you have been using Bash with little customization, give a try to ZSH with Oh My ZSH and you might be surprised by how good it is.
zprof is a profiling tool integrated into ZSH. Add zmodload zsh/zprof
at the top of your ~/.zshrc
, then start a new shell and run zprof
. This will display a breakdown of the shell's loading time. Thanks to zprof
, you can discover which parts of your .zshrc
slow down your shell.
For oh-my-zsh, my ~/.zshrc
's plugins=()
array has the following:
CTRL+O
.At the end of my ~/.zshrc
, two new files ~/.zshrc_custom.zsh
and ~/.secrets.sh
are sourced:
~/.zshrc_custom.zsh
contains all my init commands for various tools, as well as aliases~/.secrets.sh
contains API keys, passwords, etc... I separate secrets from the other files to reduce the risk of committing them by accident.To quickly open a text editor for the files above, I use these aliases:
alias zshc="$EDITOR ~/.zshrc_custom.zsh"
alias zshs="$EDITOR ~/.secrets.sh"
Here are a few notes based on my previous experience:
.zshrc
, as I always prefer to re-spawn the current tmux pane instead to get a clean environment.~/.zprofile
nor ~/.zshenv
for my init commands and aliases and prefer to keep everything in ~/.zshrc_custom.zsh
for simplicity. If ~/.zshrc_custom.zsh
becomes very big, it can be split up into smaller files to be sourced sequentially.~/.zshrc
or other files. I always move those to ~/.zshrc_custom.zsh
so everything stays in the same place.I use tmux with gpakosz's configuration to use multiple terminals in a single display. gpakosz's config improves a lot the default tmux config with better graphics and information about the system (uptime, hostname, etc...)
At the end of my .tmux.conf.local
created as part of gpakosz config setup, I have re-mapped some of the keys for my Swiss keyboard and added some new shortcuts:
bind k split-window -h
bind l split-window -v
unbind '"'
unbind %
bind j select-pane -t :.+
unbind o
bind n previous-window
bind m next-window
unbind p
# Replace current pane by a new one
bind h respawn-pane -k
# Capture current pane content to buffer
bind s capture-pane -S -1000 \; save-buffer /tmp/tmux-buffer.txt \; send-keys tmuxbuffer Enter\
In my ~/.zshrc_custom.zsh
, I have these functions:
function tmuxbuffer () {
# Open tmux buffer saved with PREFIX+S shortcut in text editor
local TMUX_BUFFER="/tmp/tmux-buffer.txt"
if [ ! -f "$TMUX_BUFFER" ]; then
echo "[ERROR] Could not find file: $TMUX_BUFFER"
return 1
fi
sed -i -e :a -e '/^\n*$/{$d;N;};/\n$/ba' "$TMUX_BUFFER"
"$EDITOR" "$TMUX_BUFFER"
}
dev-tmux () {
# Create a new tmux session with some windows opened at specific locations
# that I need frequently
local TMUX_SESSION_NAME="dev-tmux"
tmux new-session -d -s "$TMUX_SESSION_NAME" -n "firstwindow"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "todaydirs" -c "${TODAYDIRS}"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "my-frequent-dir-1" -c "${WORKSPACE_DIR}/frequent-dir-1"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "my-frequent-dir-2" -c "${WORKSPACE_DIR}/frequent-dir-2"
# Repeat at needed
tmux kill-window -t "${TMUX_SESSION_NAME}:firstwindow"
tmux select-window -t "${TMUX_SESSION_NAME}:todaydirs"
tmux attach-session -d
}
SSH is pretty ubiquitous, so I use the ~/.ssh/config
file extensively to create aliases for hosts I access often. I also use the ProxyJump option to simplify accessing hosts located behind a bastion host.
When I have poor connectivity to my target such as in the train or when accessing a machine far away, I use mosh as a substitute for SSH. Note that as of now, mosh does not support ProxyJump
.
I use ed25519 to generate my SSH key as it is more secure and results in a shorter public key. My public key is named after its creation date and the device such as 20211223-Pierre-Desktop
so it can easily be recognized by other people who don't know if they have my most recent public key.
# SSH into a machine without the host key check (avoid "someone is doing something nasty" error)
alias sshi="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
# Copy my public key to clipboard to share it quickly
alias pk="cat $HOME/.ssh/id_ed25519.pub | tr -d '\n' | clipcopy"
# Open my SSH config
alias sshc="$EDITOR $HOME/.ssh/config"
# Open my known-hosts file
alias sshkh="$EDITOR $HOME/.ssh/known_hosts"
# Open my authorized_keys file
alias sshak="$EDITOR $HOME/.ssh/authorized_keys"
# List authorized_keys names / comments
alias sshakl="cat ~/.ssh/authorized_keys | grep -Eo ' \S+\$' | grep -Eo '\S+'"
ls
with better defaults and optionstree
with an interactive file explorerfind
with better defaults and is fasterCTRL+T
) and previously used command (CTRL-R
), all with autocompletion..envrc
file in the current directory when you enter it. Instead of cluttering and slowing down ~/.zshrc_custom.zsh
with project-specific init commands and aliases, I can put them in the .envrc
at the root of the project that needs them.nano
due to its mouse support and its default shortcuts (e.g. CTRL+C, CTRL+V). I have export EDITOR=micro
to set micro
as my default editor in my ~/.zshrc_custom.zsh
.~/.config/micro/bindings.json
contains {"Ctrl-d": "Delete"}
to prevent the backspace issue described in the "Motivation" section.grep
that is faster and works with regexes by default.sed
to perform text substitution with the regex syntax of Pythoncat
with syntax highlighting. I have cat
aliased to bat
in my ~/.zshrc_custom.zsh
.top
to visualize the resource usage of processes, including a tree view. I have aliased htop
to alias htop="htop --tree"
in my ~/.zshrc_custom.zsh
so it shows the tree view by default.du
to show a breakdown of the disk usage of a set of files/directoriesdf
to show system-wide disk usageThis is a list of aliases that replace some of the most used commands by their modern alternative, or that add some more sensible defaults.
# Always enable colored "grep" output
alias grep='grep --color=auto '
# copy/move ask for permission
alias cp='cp -i'
alias mv='mv -i'
# on macOS
# alias sed="gsed"
# alias date="gdate"
# Modern tools replace old ones
alias cat="bat --paging never --plain"
alias catp="bat"
# If we need to unalias entries from common-aliases from Oh-My-ZSH
# for COMMAND in l ll la; do unalias \$COMMAND; done
alias ls='exa' # ls
alias l='exa -lbFa --git' # list, size, type, git
alias ll='exa -lbGFa --git' # long list
alias llm='exa -lbGda --git --sort=modified' # long list, modified date sort
alias la='exa -lbhHigUmuSa --time-style=long-iso --git --color-scale' # all list
alias lx='exa -lbhHigUmuSa@ --time-style=long-iso --git --color-scale' # all + extended list
In addition, I use tldr to print more user-friendly man pages.
I only use Git for version control. On some projects that involve large amounts of test data (e.g. video data for computer vision), I set up Git LFS.
To get better diffs with Git, I use diff-so-fancy. To visualize changes in a nice terminal GUI, I use lazygit with alias lg=lazygit
.
For Git-specific aliases, I use the ~/.gitconfig
file:
[user]
name = My Name
email = myemail@example.com
[init]
defaultBranch = master
[pull]
rebase = false
[submodule]
recurse = true
# Integration with diff-so-fancy
[core]
pager = diff-so-fancy | less --tabs=4 -RFX
[interactive]
diffFilter = diff-so-fancy --patch
[alias]
# Committing
ap = add .
cm = commit -m
cmwip = !git add -A && git commit -m "WIP"
amend = commit --amend
unstage = reset HEAD
undo = reset HEAD~1
undo-hard = reset --hard HEAD~1
cp = cherry-pick
# Branches
s = status
f = fetch
pl = pull
co = checkout
cob = checkout -b
sync-master = !git checkout master && git pull --rebase
# History
rbi = rebase -i
rbm = rebase origin/master
rbmi = rebase -i origin/master
rbn = rebase origin/main
rbni = rebase -i origin/main
pushf = push --force-with-lease
rho = !git reset --hard origin/$(git rev-parse --abbrev-ref HEAD)
# Diffs
d = diff --patch-with-stat
dc = diff --cached
di = !"d() { git diff --patch-with-stat HEAD~$1; }; git diff-index --quiet HEAD -- || clear; d"
# Lists
tags = !git tag -l | cat
branches = !git branch -a | cat
latest-branches = !git branch -a --sort=-committerdate | head -n 20
remotes = !git remote -v | cat
latest = !sh -c 'git log origin/master --pretty=format:%H --reverse | head -1'
unreleased = "!f() { git fetch --tags && git diff $(git tag | tail -n 1); }; f"
graph = log --graph --color --pretty=format:"%C(yellow)%H%C(green)%d%C(reset)%n%x20%cd%n%x20%cn%x20(%ce)%n%x20%s%n"
aliases=!git config -l | grep ^alias | cut -c 7- | sort
# Find
find-branches-by-commit = "!f() { git branch -a --contains $1; }; f"
find-tags-by-commit = "!f() { git describe --always --contains $1; }; f"
find-commits-by-source = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short -S$1; }; f"
find-commits-by-message = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f"
VS Code is my main text editor. I use its remote development features extensively, including Remote - SSH
. In addition to the most common language- and tool-specific plugins (e.g. Docker, Prettier, Better TOML), I use the following general-purpose plugins:
git blame
output for the currently active lineHere are my config changes from the defaults:
{
"telemetry.enableCrashReporter": false,
"telemetry.enableTelemetry": false,
"editor.minimap.enabled": false,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.rulers": [89]
}
Here are some aliases I have to open VS Code from the terminal:
alias vsc="code ."
alias vscn="code --new-window"
alias vscr="code --reuse-window"
For HTTP and DNS, I use the following:
curl
dig
I use Docker extensively to work with containers. For visualization purposes, I use the following tools:
Python is the main programming language I have been using the past few years. I used to manage my Python interpreters and virtual environments with pyenv, but have recently written my own tool to replace it: lonesnake.
I use lonesnake
to create a global Python environment for my user account, and then also to create local environments for each of my Python projects. In my global environment, I use pipx to install the command-line tools that happen to be written in Python.
Instead of working inside ~/Documents
or ~/Desktop
or ~/Downloads
, I keep everything in a ~/workspace
directory.
Within ~/workspace
, I have a special directory called dailydirs
containing 1 directory for each day with my work files for that day. This is easier to manage than keeping all files in the ~/Downloads
directory.
In addition, I have a few aliases such as todaydir
to access the workspace or daily directories.
WORKSPACE_DIR="$HOME/workspace"
alias wks="cd \"$WORKSPACE_DIR\""
mkdir -p "$WORKSPACE_DIR"
DAILY_DIRS="${WORKSPACE_DIR}/dailydirs"
mkdir -p "$DAILY_DIRS"
alias todaydir='mkdir -p "${DAILY_DIRS}/$(date +%Y%m%d)-dailydir/" && cd "${DAILY_DIRS}/$(date +%Y%m%d)-dailydir/"'
Given that I have a lot of configuration and several dotfiles, my dev environment is a great candidate for version control. chezmoi provides user-friendly commands to manage dotfiles with Git and to "apply" them to a new computer in seconds. My dotfiles are stored in my dotfiles repo on GitHub.
In terms of desktop apps, I use:
On Ubuntu, I use xclip
to copy to and from the clipboard with my aliases.
On macOS, I have a few specific apps:
CTRL+OPTION+LEFT
, right half as CTRL+OPTION+RIGHT
, previous display as CTRL+OPTION+CMD+LEFT
, next display as CTRL+OPTION+CMD+RIGHT
, full screen as CTRL+OPTION+F