Christoph Nakazawa

The Perfect Development Environment

Small optimizations to your coding setup yield massive productivity gains over time. It’s a great way to take control of your Developer Experience and make everyday tasks more delightful. While the perfect development environment doesn’t exist, you can spend time making your setup perfect for you.

In this guide I’m recommending apps, tools and programs that reduce context switching, provide a consistent experience and make me go faster. If you haven’t checked out the previous post on setting up a Mac, I suggest starting there. If you want to follow along or just copy-paste my setup, check out my dotfiles repo cpojer/dotfiles. To get all the apps listed below immediately, run brew bundle within a clone of my dotfiles repo.

iTerm2 & Fish shell

iTerm2 is my terminal choice for macOS.1 It is fast, reliable and feature-rich. While Zsh has become the default on macOS, and my dotfiles have a reasonable Zsh setup, I’ve found that it’s a lot of work to get good defaults with Zsh. This is why I’ve been using fish shell for the past several years.2 It comes with sensible defaults and autocomplete right out of the box. You can try fish shell in your browser or check out this awesome list of fish plugins and protips. This is my setup:

To replicate this setup, run: brew install eza fish font-fira-code font-fira-code-nerd-font iterm2 and set /opt/homebrew/bin/fish as the “Command” in your default iTerm2 profile. Let’s walk through what’s in the video:

  • eza: A modern replacement for ls with file type icons.3
  • nerd fonts”: These fonts are variations of popular fonts combined with a large number of popular glyphs. These icons and glyphs can add better visual hints to your terminal output, such as file type icons when listing files in a directory. If you are using Homebrew you can just install most fonts with a -nerd-fonts suffix, for example brew install font-fira-code-nerd-font.
  • z: Jump through favorite directories quickly.
  • vcs: Print the current branch and status of a (git) repository as part of the prompt.
  • Custom theme: In the “Colors” panel for your default profile, you can choose themes to match your text editor or make a custom theme. It’s great for consistency across your tools!

If you like ligatures, you can enable them in the iTerm2 Text preferences:

iTerm2 Text Preferences

If you are automatically switching between light and dark mode on your system, check “Use different colors for light and dark mode” in the Colors section.

I also love the Shortcut key feature. You can set up a global shortcut to quickly bring iTerm2 to the foreground or background. This makes it easier to quickly bring iTerm2 to the front or hide it. You can set this up in the iTerm2 preferences for your default profile. I have mine set up to ⌃ control + z.

When I install new tools like eza, I tend to never use them and completely forget about them and it’s better to overwrite existing commands like ls. You could use fish aliases, but the automatic expansion for such commonly used commands is distracting. I created custom fish functions to map eza to ls in my .config/fish/functions folder:

fish
# ~/.config/fish/functions/ls.fish
function ls --description 'Use eza instead of ls'
eza -a --icons --no-user --no-time $argv
end
# ~/.config/fish/functions/l.fish
function l --description 'Use eza instead of ls'
eza -al --icons --no-user --no-time $argv
end

The equivalent syntax for Zsh and Bash is alias ls="eza -a --icons --no-user --no-time".

Note: The VS Code Terminal is a good alternative but using a terminal is not always bound to a specific (coding) project so you'll most likely want to keep a terminal app like iTerm2 around.

Git + gh

Despite recent improvements to Git’s user experience, Git is still complicated to use and requires typing long and cryptic commands. I set up a few shortcuts in my .gitconfig file to make things a bit easier:

ini
[alias]
st = status
amend = commit --amend --no-edit
a = commit --amend --no-edit
co = checkout
br = branch
cp = cherry-pick
rb = pull --rebase
rbu = pull --rebase upstream main
logo = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ (%cn)" --decorate

I also have a bunch of fish aliases in my config.fish file:

fish
abbr --add am 'git amend'
abbr --add g 'git'
abbr --add gbr 'git br'
abbr --add gco 'git co'
abbr --add gd 'git diff -w'
abbr --add gds 'git diff -w --staged'
abbr --add glogf 'git logf'
abbr --add glogo 'git logo'
abbr --add gp 'git push origin'
abbr --add gs 'git status'

These shortcuts significantly reduce the amount of typing when working with Git. If you autoformat your code with prettier or other formatting tools, whitespace changes clutter your diff views so I recommend always using -w to ignore whitespace. Here is an example of my Git setup:

Honestly though, adding so many shortcuts in addition to existing commands can make version control even more confusing. The way I got used to Git was by printing out a cheat sheet and putting it on my desk. I added shortcuts whenever I felt like I got a good handle of the current set of commands and I wanted to get faster at using them. Just using gd for git diff and gs for git status made me go a lot faster.

gh is the official GitHub client. If you are still using hub I suggest switching to gh. While this cli is powerful, I mostly use it for gh browse to quickly open a browser window for the current repo.

Bat and Delta

As you can see in the above videos, I’m using new command line utilities that reinvent some common command-line programs:

  • bat: A clone of cat with syntax highlighting.
  • delta: A syntax-highlighting diff tool for the command line, using bat for syntax highlighting.
Syntax Themes

You can install them by running brew install bat git-delta and adding the following to your .gitconfig to use delta together with git diff and have it automatically handle dark or light themes on your Mac:

ini
[core]
pager = delta --features "$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo default || echo GitHub)"
[interactive]
diffFilter = delta --color-only
[add.interactive]
useBuiltin = false
[delta]
navigate = true # Use n and N to move between diff sections.
light = false # Set to `true` if you prefer light themes.
side-by-side = true # Enables the side-by-side view.
[merge]
conflictstyle = diff3
[diff]
colorMoved = default

I added this bat.fish file in my .config/fish/functions folder which overwrites cat and automatically chooses the right syntax theme based on whether I use light or dark mode on my system:

fish
function cat --description 'Use bat instead of cat'
bat --theme=(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo default || echo GitHub) $argv
end

I love that you can drop a TextMate theme into the bat themes folder and use custom syntax themes for bat and git diff. Just make sure you don’t forget to run bat cache --build when you add a new theme. It took me quite a while to optimize the delta output to mirror my custom themes. If you are not happy with the default output and want to replicate my setup, add the following to your .gitconfig:

ini
[core]
pager = delta --features "$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo dunkel-theme || echo licht-theme)"
[delta "licht-theme"]
light = true
side-by-side = true
file-style = "bold #111111"
file-decoration-style = "#111111 ul"
grep-file-style = "bold #111111"
grep-line-number-style = "#adadad"
hunk-header-decoration-style = "#adadad ul ol"
hunk-header-line-number-style = "#111111"
line-numbers = true
line-numbers-left-style = "#adadad"
line-numbers-right-style = "#adadad"
line-numbers-zero-style = "#adadad"
navigate = true
syntax-theme = Licht
tabs = 2
map-styles = \
bold purple => syntax "#feecf7", \
bold blue => syntax "#e5dff6", \
bold cyan => syntax "#d8fdf6", \
bold yellow => syntax "#f4ffe0"
[delta "dunkel-theme"]
light = false
side-by-side = true
file-style = "bold #c8c8c8"
file-decoration-style = "#c8c8c8 ul"
grep-file-style = "bold #c8c8c8"
grep-line-number-style = "#adadad"
hunk-header-decoration-style = "#adadad ul ol"
hunk-header-line-number-style = "#c8c8c8"
line-numbers = true
line-numbers-left-style = "#adadad"
line-numbers-right-style = "#adadad"
line-numbers-zero-style = "#adadad"
navigate = true
syntax-theme = Dunkel
tabs = 2

Custom Light and Dark Themes: Licht & Dunkel

Back in 2003 I started using “Zend Studio” for PHP and frontend development, which is likely how I ended up preferring light syntax themes, although recently I’ve been using a dark theme at night. I made dozens of tweaks along the way from Zend Studio, to TextMate, Sublime Text and now VS Code. They are published as licht-theme and dunkel-theme (German for “light” and “dark”). For consistency, I use them in all tools/text editors including the code examples on my blog.

Syntax Themes

I switch between light and dark mode on my Mac depending on the daytime. I have every app set up to automatically switch the syntax theme as well. Check out this commit in my dotfiles repo for more information about how to do that for bat, git-delta, VS Code and Sublime Text.

Note:

I’m not here to convince you that these themes are better than others. Everyone experiences colors differently, these are working well for me, and most likely a custom theme will work better for you than a generic one. There is an awesome online VS Code theme editor, give it a try!

VS Code

There are various syncing mechanisms for VS Code, but I prefer to keep my settings in git as part of my dotfiles. I do this by moving VS Code’s User folder into my dotfiles repository and then running a setup script that symlinks the folder back into the VS Code Application Support folder. The same approach also works for Sublime Text.

bash
vscode_dir=~/Library/Application\ Support/Code
ln -s "$PWD/User" "$vscode_dir/User"

Favorite Extensions

My setup script also has a list of all the extensions I’m using which can be generated with code --list-extensions:

bash
code --install-extension adpyke.codesnap
code --install-extension Atishay-Jain.All-Autocomplete
code --install-extension bradlc.vscode-tailwindcss
code --install-extension christian-kohler.npm-intellisense
code --install-extension cnakazawa.dunkel-theme
code --install-extension cnakazawa.licht-theme
code --install-extension dbaeumer.vscode-eslint
code --install-extension eamodio.gitlens
code --install-extension esbenp.prettier-vscode
code --install-extension GitHub.copilot
code --install-extension GraphQL.vscode-graphql
code --install-extension GraphQL.vscode-graphql-syntax
code --install-extension ms-vscode-remote.remote-containers
code --install-extension ms-vscode.live-server
code --install-extension nhoizey.gremlins
code --install-extension Prisma.prisma
code --install-extension silvenon.mdx
code --install-extension usernamehw.errorlens
code --install-extension ziyasal.vscode-open-in-github
code --install-extension znck.grammarly

Among my favorite extensions are All Autocomplete to expand auto-complete suggestions, Error Lens for inline errors and Codesnap for quickly sharing code screenshots. Error Lens in particular significantly speeds up my workflow by showing me errors inline as I write code:

Screenshot of VS Code with ErrorLens

Protip: Enable fast “jump-to-definition” Behavior

VS Code recently changed the “jump-to-definition” behavior for ⌘ command + click to show all possible definitions in a popup instead of directly jumping to the first definition. This is especially frustrating when navigating through React components as you’ll rarely want to peek through to React’s internal definitions and always jump straight to the component’s implementation. Here is how you restore the fast behavior:

json
{
"editor.gotoLocation.multipleDefinitions": "goto"
}

Protip: Disable “acceptSuggestionOnCommitCharacter”

It’s common to have similar names like Player and PlayerID in a project. For the longest time when I was typing for example Player;, VS Code would autocomplete it to PlayerID; because semicolons in VS Code with TypeScript set as syntax act as “commit character”. It was infuriating. Thankfully, this behavior can be disabled:

json
{
"editor.acceptSuggestionOnCommitCharacter": false
}

I recommend checking out my settings.json for more tips and optimizations.4

Protip: Autofix ESLint issues on save

If you are using ESLint, you can automatically fix issues on save by adding the following to your settings.json:

json
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}

This is a huge time saver while coding. For example, if you are using @nkzw/eslint-config it will automatically remove unused imports and sort object keys alphabetically:

Keybindings

Many years ago Sublime Text introduced me to ⌃ control + ← left and ⌃ control + → right for moving the cursor by word parts. This is incredibly useful when dealing with camelCase or snake_case in code. Then mac OS introduced virtual desktops and overwrote the keyboard shortcut system-wide. Chances are you aren’t using those and you can disable “Move left a space” and “Move right a space” in System PreferencesKeyboardShortcutsMission Control:

mac OS Keyboard Shortcuts

Disabling the mission control shortcuts frees them up for use in VS Code. Try adding the following to your keybindings.json:

json
[
{
"key": "ctrl+left",
"command": "cursorWordPartLeft",
"when": "textInputFocus"
},
{
"key": "ctrl+alt+left",
"command": "-cursorWordPartLeft",
"when": "textInputFocus"
},
{
"key": "ctrl+shift+left",
"command": "cursorWordPartLeftSelect",
"when": "textInputFocus"
},
{
"key": "ctrl+right",
"command": "cursorWordPartRight",
"when": "textInputFocus"
},
{
"key": "ctrl+alt+right",
"command": "-cursorWordPartRight",
"when": "textInputFocus"
},
{
"key": "ctrl+shift+right",
"command": "cursorWordPartRightSelect",
"when": "textInputFocus"
}
]

Navigating by word parts especially shines when using shift to select parts of a word and expanding it to multiple cursors for refactoring:

If you are a fan of Sublime Text’s keyboard shortcuts, my keybindings.json are heavily inspired by them.

This reminds me of one of my favorite terminal shortcuts: abbr --add c 'code .': Simply typing c and hitting enter will open the current directory in VS Code!

Refined GitHub Chrome Extension

If you are spending your day on GitHub, Refined GitHub is a must-have extension. It enhances and improves the user experience of GitHub significantly – or rather: It fixes almost every little issue you might have with GitHub’s user interface. To stick with the theme of consistency, if you want to use your coding font on GitHub to make code reviews feel closer to what they look like in your text editor, drop this into Refined GitHub’s Custom CSS extension settings:

css
code,
kbd,
pre,
tt,
.ace_editor.ace-github-light,
.blame-sha,
.blob-code-inner,
.blob-num,
.branch-name,
.commit .sha,
.commit .sha-block,
.commit-desc pre,
.commit-ref,
.commit-tease-sha,
.default-label .sha,
.export-phrase pre,
.file-editor-textarea,
.file-info,
.gollum-editor .expanded textarea,
.gollum-editor .gollum-editor-body,
.hook-delivery-guid,
.hook-delivery-response-status,
.input-monospace,
.news .alert .branch-link,
.oauth-app-info-container dl.keys dd,
.quick-pull-choice .new-branch-name-input input,
.tag-references > li.commit,
.two-factor-secret,
.upload-files .filename,
.url-field,
.wiki-wrapper .wiki-history .commit-meta code {
font-family: 'Fira Code' !important;
}

Build One-Off Tools

My final recommendation is to build one-off tools for common workflows that slow you down. Not only will they improve productivity for you, but they will likely also help others. That’s a good way to dabble in open source or build internal tooling within your company. For example, one time I built a Chrome Extension to display an internal logging tool which made it much easier to access, or another time my colleagues and me built a global cli and added dozens of useful commands to speed up various development workflows.


We are all different, and we experience colors, sounds and animations differently. For example, I disable all system sounds because they take me out of my focus while other folks enjoy hearing ding sounds for every email they receive. The above tips and tricks make me go a lot faster and they might help you improve your workflows, but chances are that you can find even better ways to improve your productivity.

Footnotes

  1. The app is called iTerm2, but the current major version is 3. So technically it is called iTerm2 v3.

  2. As noted in Principles of DevX, fish shell’s principles are great principles for any dev tool.

  3. eza is a fork of exa, which became unmaintained.

  4. If you have been reading this far, it won’t be surprising to hear that "editor.fontLigatures": true is one of my favorite settings.

Tweet about this post, or share it with your friends. Thank you for reading, and have a great day!

Check out Athena Crisis

Athena Crisis

Ready for Another Post?

Subscribe for More