Your dev environment is your ideal supermarket
My best tools, dotfiles, and shopping experience

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.

terminal

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.

shell

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:

  • zsh-autosuggestions suggests the reminder of the command in darker text based on what you started to type and your history
  • zsh-syntax-highlighting colors the currently typed command to help catch syntax errors
  • copydir copies the current working directory to the clipboard
  • copyfile takes a file as argument and copies its content to the clipboard
  • copybuffer copies the current command you have typed to the clipboard when pressing CTRL+O.
  • you-should-use reminds you to use the aliases you set when you don't

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:

  • I don't have any alias to source .zshrc, as I always prefer to re-spawn the current tmux pane instead to get a clean environment.
  • I don't use ~/.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.
  • Sometimes the installer of a tool will add some lines to your ~/.zshrc or other files. I always move those to ~/.zshrc_custom.zsh so everything stays in the same place.

tmux

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
}

remote management with SSH

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+'"
  • exa replaces ls with better defaults and options
  • broot replaces tree with an interactive file explorer
  • fd replaces find with better defaults and is faster
  • fzf adds two keyboard shortcuts to easily browse previously visited directories (CTRL+T) and previously used command (CTRL-R), all with autocompletion.
  • thefuck corrects the previous command by typing the eponymous curse word
  • zoxide remembers the directories you visit and lets you jump to most frequently visited directory by typing just a small portion of their path
  • direnv automatically loads an .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.
  • micro is my main text editor in the terminal. It is much more user-friendly than 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.
  • ag is a replacement of grep that is faster and works with regexes by default.
  • sd is a replacement for sed to perform text substitution with the regex syntax of Python
  • bat is a replacement for cat with syntax highlighting. I have cat aliased to bat in my ~/.zshrc_custom.zsh.

OS metrics

  • htop is a replacement for 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.
  • dust is a replacement for du to show a breakdown of the disk usage of a set of files/directories
  • duf is a replacement for df to show system-wide disk usage
  • glances shows all kinds of system metrics in a big dashboard

better defaults and modern replacements

This 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.

Git

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

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:

  • Error Lens to make syntax or linting violations more visible
  • Git Lens discreetly shows git blame output for the currently active line
  • indent-rainbow shows each indentation level with a different color
  • TODO Highlight shows TODO-type messages with a colored background
  • vscode-icons adds more icons for files

Here 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"

back-end tools

For HTTP and DNS, I use the following:

  • httpie performs HTTP requests with a better UX than curl
  • dog performs DNS queries with more readables answers than dig

I use Docker extensively to work with containers. For visualization purposes, I use the following tools:

  • ctop shows a breakdown of the resource usage of all containers
  • lazydocker shows a dashboard with all available information about Docker images and containers.

Python

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.

workspace and note management

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/"'

dotfiles management

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.

bonus section: desktop apps

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:

  • Rectangle to organize window layout with keyboard shortcuts
    • configure it with left half as 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
  • Flycut to keep the last 100 clipboard entries and easily paste any of them with keyboard shortcuts