# The Perfect Development Environment - Christoph Nakazawa

Small optimizations to your coding setup yield massive productivity gains over time. It's a great way to [take control of your Developer Experience](/posts/principles-of-devx) 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](/posts/set-up-a-new-mac-fast), I suggest starting there. If you want to follow along or just copy-paste my setup, check out [my dotfiles repo `cpojer/dotfiles`](https://github.com/cpojer/dotfiles). To get all the apps listed below immediately, run `brew bundle` within a clone of my dotfiles repo.

## Ghostty & Fish shell

[Ghostty](https://ghostty.org/) is my terminal choice for macOS.^[I previously used iTerm 2 for more than 15 years and it used to be a great choice.] It is modern, fast, reliable and feature-rich. While [Zsh](https://zsh.sourceforge.io/) 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](https://fishshell.com/) for the past several years.^[As noted in [Principles of DevX](/posts/principles-of-devx), [fish shell's principles](http://fishshell.com/docs/current/design.html) are great principles for any dev tool.] It comes with sensible defaults and autocomplete right out of the box. You can [try fish shell in your browser](https://rootnroll.com/d/fish-shell/) or check out this [awesome list of fish plugins and protips](https://github.com/jorgebucaran/awsm.fish). This is my setup:

<Video src="/blog/the-perfect-development-environment/Ghostty.webm" />

To replicate this setup, run: `brew install eza fish font-fira-code font-fira-code-nerd-font ghostty` and add `command = /opt/homebrew/bin/fish` to your Ghostty config. Let's walk through what's in the video:

- **[`eza`](https://github.com/eza-community/eza):** A modern replacement for `ls` with file type icons.^[`eza` is a fork of `exa`, which became unmaintained.]
- **"[nerd fonts](https://github.com/ryanoasis/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`](https://github.com/jethrokuan/z):** Jump through favorite directories quickly.
- **[`vcs`](https://github.com/oh-my-fish/plugin-vcs):** Print the current branch and status of a (git) repository as part of the prompt.

I also love the global shortcuts of Ghostty. You can set up a global shortcut to quickly bring Ghostty to the foreground or move it to the background. I added `keybind = global:ctrl+y=toggle_visibility` to the settings, so ctrl+y will toggle visibility of Ghostty.

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 children="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 Ghostty around." />

## Git + gh

Despite [recent improvements](https://www.banterly.net/2021/07/31/new-in-git-switch-and-restore/) 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. I recommend always using `-w` to ignore whitespace when reviewing diffs locally. Here is an example of my Git setup:

<Video src="/blog/the-perfect-development-environment/Git.webm" />

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`](https://github.com/cli/cli) is the official GitHub client. If you are still using [`hub`](https://github.com/github/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`](https://github.com/sharkdp/bat#readme):** A clone of `cat` with syntax highlighting.
- **[`delta`](https://github.com/dandavison/delta#readme):** A syntax-highlighting diff tool for the command line, using `bat` for syntax highlighting.

<Image
  alt="Syntax Themes"
  src="/blog/the-perfect-development-environment/Delta.png"
  hasVariants={false}
/>

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](https://github.com/sharkdp/bat#adding-new-themes) 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`](https://marketplace.visualstudio.com/items?itemName=cnakazawa.licht-theme) and [`dunkel-theme`](https://marketplace.visualstudio.com/items?itemName=cnakazawa.dunkel-theme) (German for "light" and "dark"). For consistency, I use them in all tools/text editors including the [code examples on my blog](/posts/building-a-javascript-bundler).

<Image
  alt="Syntax Themes"
  src="/blog/the-perfect-development-environment/Themes.png"
  hasVariants={false}
/>

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](https://github.com/cpojer/dotfiles/commit/adde6c962f726ca96eab235ea9c53c962426c4c1) 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](https://themes.vscode.one/), give
  it a try!
</Note>

## VS Code

There are various syncing mechanisms for VS Code, but I prefer to keep my settings [in git as part of my dotfiles](https://github.com/cpojer/dotfiles/tree/main/vscode). 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](https://github.com/cpojer/dotfiles/tree/main/sublime).

```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 bazelbuild.vscode-bazel
code --install-extension bradlc.vscode-tailwindcss
code --install-extension cardinal90.multi-cursor-case-preserve
code --install-extension christian-kohler.npm-intellisense
code --install-extension cnakazawa.dunkel-theme
code --install-extension cnakazawa.licht-theme
code --install-extension docker.docker
code --install-extension eamodio.gitlens
code --install-extension formulahendry.auto-rename-tag
code --install-extension github.copilot
code --install-extension github.copilot-chat
code --install-extension github.vscode-github-actions
code --install-extension graphql.vscode-graphql
code --install-extension graphql.vscode-graphql-syntax
code --install-extension johnsoncodehk.vscode-code-spotlight
code --install-extension markosth09.color-picker
code --install-extension mathiasfrohlich.kotlin
code --install-extension mikestead.dotenv
code --install-extension mrmlnc.vscode-duplicate
code --install-extension ms-azuretools.vscode-docker
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 rust-lang.rust-analyzer
code --install-extension sysoev.vscode-open-in-github
code --install-extension tamasfe.even-better-toml
code --install-extension unifiedjs.vscode-mdx
code --install-extension usernamehw.errorlens
code --install-extension vunguyentuan.vscode-css-variables
code --install-extension wix.vscode-import-cost
```

Among my favorite extensions are [All Autocomplete](https://marketplace.visualstudio.com/items?itemName=Atishay-Jain.All-Autocomplete) to expand auto-complete suggestions, [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) for inline errors and [Codesnap](https://marketplace.visualstudio.com/items?itemName=adpyke.codesnap) for quickly sharing code screenshots. Error Lens in particular significantly speeds up my workflow by showing me errors inline as I write code:

<Image
  alt="Screenshot of VS Code with ErrorLens"
  src="/blog/the-perfect-development-environment/ErrorLens.png"
/>

### 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`](https://github.com/cpojer/dotfiles/blob/main/vscode/User/settings.json) for more tips and optimizations.^[If you have been reading this far, it won't be surprising to hear that `"editor.fontLigatures": true` is one of my favorite settings.]

### 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`](https://github.com/cpojer/eslint-config) it will automatically remove unused imports and sort object keys alphabetically:

<Video src="/blog/the-perfect-development-environment/ESLint.webm" />

### 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 Preferences` → `Keyboard` → `Shortcuts` → `Mission Control`:

<Image
  alt="mac OS Keyboard Shortcuts"
  src="/blog/the-perfect-development-environment/KeyboardShortcutsMissionControl.png"
/>

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:

<Video src="/blog/the-perfect-development-environment/Keyboard.webm" />

If you are a fan of Sublime Text's keyboard shortcuts, [my `keybindings.json`](https://github.com/cpojer/dotfiles/blob/main/vscode/User/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](https://github.com/refined-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.
