Mac Development Toolchain Configuration Notes

This page is machine-translated and may contain errors. Read the Chinese original

Recently I set up a complete development toolchain on my Mac from scratch — from terminal emulator to editor, from dotfiles management to shell theming — and it took about a weekend of tinkering. Before this, I had been using everything piecemeal, and every time I switched devices I had to reconfigure everything from scratch. This time I unified management with chezmoi and tried out some new tools along the way.

This post records my current tool stack and configuration approach, mainly targeting Unix systems (Mac), with a possible future migration to Linux.

1. Dotfiles and Package Management

Dotfiles Manager — chezmoi

On Unix systems, configurations for all kinds of tools are almost always stored as hidden files in ~/.config or the home directory. Git, Zsh, Vim, tmux... all configurations are plain-text dotfiles. The question is: how do you sync these configurations across devices while protecting sensitive information like API keys?

A common approach is to create a Git repository and use symbolic links (ln -s) to map configuration files to the home directory, or manage them with tools like Stow/RCM. But the symlink approach has several pain points:

  • Cross-device configuration differences are hard to handle (Mac and Linux servers may need different configurations)
  • Secrets (API keys, tokens) can't go directly into a public repository
  • After downloading, you need to manually adjust permissions and paths

chezmoi solves these problems in a way that's more like a "template engine." It doesn't blindly copy or link files; instead, it dynamically generates the final dotfiles based on system variables, template syntax, and secret managers.

Daily workflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装
brew install chezmoi

# 初始化仓库(存放位置 ~/.local/share/chezmoi)
chezmoi init

# 添加需要管理的配置文件
chezmoi add ~/.zshrc
chezmoi add ~/.config/nvim/init.lua

# 编辑(使用 $EDITOR 指定的编辑器)
chezmoi edit ~/.zshrc

# 查看修改差异
chezmoi diff

# 应用修改到实际配置文件
chezmoi apply

One small detail: chezmoi edit modifies the copy in the chezmoi repository, and you must run chezmoi apply for it to be written to the actual path. This mechanism gives you a chance to compare and confirm before applying, so it's hard to mess up.

If the configuration file itself is a symbolic link, chezmoi add needs the -f flag to follow the symlink and read the actual file contents.

Package Manager — Homebrew

For development on macOS, Homebrew is basically a must-have. The official installation script often fails to download under domestic network conditions, so I used a mirror script from Gitee:

1
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

After installation, I added a few optimization options to .zshrc:

1
2
3
4
5
6
# 关闭 Homebrew 自动更新(需要时手动 brew update)
export HOMEBREW_NO_AUTO_UPDATE=1
# 关闭自动显示更新提示
export HOMEBREW_NO_ENV_HINTS=1
# 关闭 analytics(隐私+速度)
export HOMEBREW_NO_ANALYTICS=1

These switches make each brew install much faster, since you don't have to wait for it to auto-update.

2. Terminal and Shell Layer

This layer is where day-to-day interaction happens most frequently, covering everything from the terminal emulator, shell, plugins, to the prompt.

Terminal Emulator — Alacritty

The reason I chose Alacritty over terminal tools like Ghostty, Kitty, or iTerm is that I don't really want a bunch of features I'll never use getting downloaded, or having one feature implemented by two tools at the same time.

For example, terminal multiplexing: most terminals come with built-in multiplexing, but since I use tmux regularly myself, using a terminal emulator with built-in multiplexing would be redundant. There are a few similar reasons.

Alacritty is written in Rust, so it's faster and uses less memory.

Installation:

1
brew install alacritty

On macOS, the first time you open it you need to allow it in System Security settings. Also, the original icon really isn't great; you can refer to this Zhihu link to swap it out.

My configuration is as follows (~/.alacritty.toml), and more configuration options can be found in the official documentation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[window]
# 窗口尺寸(字符列数 × 行数)
dimensions = { columns = 80, lines = 24 }
# 窗口内边距
padding = { x = 4, y = 0 }
# 背景不透明度 + 模糊
opacity = 0.2
blur = true
# 启动时全屏
startup_mode = "Fullscreen"
title = " My alacritty terminal "

[font]
normal = { family = "JetBrainsMono NF", style = "Regular" }
bold = { family = "JetBrainsMono NF", style = "bold" }
italic = { family = "JetBrainsMono NF", style = "italic" }
bold_italic = { family = "JetBrainsMono NF", style = "bold italic" }
size = 18

[cursor]
# 线状光标,闪烁
style = { shape = "Beam", blinking = "On" }

The key bindings are configured with macOS-style shortcuts (Cmd+C/V copy/paste, Cmd+F find, Cmd+N new window, etc.), so I won't paste all of them here.

Shell Core — Zsh + Antidote

I don't use Oh My Zsh. Although it's really convenient, its startup speed isn't great, and it loads a ton of stuff you'll probably never use.

Final choice: vanilla Zsh + Antidote as the plugin manager.

Compared to Oh My Zsh and zinit, Antidote's advantages are simplicity, fast startup, and support for bridging ohmyzsh plugins. Installation:

1
brew install antidote

The initialization logic in .zshrc:

1
2
3
4
5
6
7
8
9
10
# antidote 初始化
source $(brew --prefix antidote)/share/antidote/antidote.zsh
# 插件配置文件
zsh_plugins=${ZDOTDIR:-~}/.zsh_plugins
# 自动生成插件缓存
if [[ ! ${zsh_plugins}.zsh -nt ${zsh_plugins}.txt ]]; then
(antidote bundle <${zsh_plugins}.txt >|${zsh_plugins}.zsh)
fi
# 加载所有插件
source ${zsh_plugins}.zsh

The caching mechanism is key: it only regenerates .zsh_plugins.zsh when .zsh_plugins.txt has updates, and daily startup loads the cached zsh file directly, so there's almost no perceptible latency.

Plugin list (~/.zsh_plugins.txt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 语法高亮
zsh-users/zsh-syntax-highlighting

# 自动补全建议
zsh-users/zsh-autosuggestions

# 历史命令搜索
zsh-users/zsh-history-substring-search

# 快速目录跳转
agkozak/zsh-z

# 使用 ohmyzsh 插件(桥接器)
getantidote/use-omz
ohmyzsh/ohmyzsh path:plugins/copyfile
ohmyzsh/ohmyzsh path:plugins/copypath
ohmyzsh/ohmyzsh path:plugins/copybuffer
ohmyzsh/ohmyzsh path:plugins/extract
ohmyzsh/ohmyzsh path:plugins/colored-man-pages

Several commonly used ohmyzsh plugins are brought in through the bridge use-omz, which is more memory-efficient than loading the entire ohmyzsh library. zsh-z is for quickly jumping between directories, and extract extracts all kinds of archives with one command — both are used constantly.

.zshrc also contains some non-toolchain configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 关闭 Homebrew 自动更新(前面提过)
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_ENV_HINTS=1
export HOMEBREW_NO_ANALYTICS=1

# 设置默认编辑器
export EDITOR="nvim"

# nvm 加载
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

# 自定义命令路径
export PATH="$HOME/.local/bin:$PATH"

Prompt — Starship

Starship is a cross-shell prompt tool written in Rust. Its standout feature is that it's fast, and it comes with beautiful preset themes out of the box.

1
brew install starship

Initialize it in .zshrc:

1
eval "$(starship init zsh)"

I went straight with the official preset Pastel Powerline; you can check the official documentation for community preset configurations and specific setup instructions. This preset displays user info, the current directory, Git branch and status, various language versions, and the current time, with nicely layered color segments. With chezmoi managing it, everything stays consistent across all my devices.

Font — Nerd Font

Nerd Font is the foundation for displaying icons and special symbols in the terminal; both Alacritty and Starship depend on it.

1
2
3
4
# 先搜一下有哪些字体可以使用
brew search nerd-font
# 选 JetBrains Mono
brew install --cask font-jetbrains-mono-nerd-font

JetBrains Mono — clear glyphs, comfortable monospacing, and well optimized for programming scenarios.

3. Terminal Multiplexer — Tmux

tmux is basically a must-have for development: a single session can be split into multiple panes, and your working environment persists after disconnecting from SSH (server-training friendly).

Installation:

1
brew install tmux

My configuration (~/.tmux.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 前缀键改为 Ctrl+a(比默认的 Ctrl+b 更顺手)
unbind C-b
set -g prefix C-a
bind C-a send-prefix

# 基础行为
set -g default-terminal "tmux-256color"
set -g history-limit 10000
set -g escape-time 0
set -g focus-events on
set -g mouse on

# 索引从1开始(而不是0)
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on

# 窗格分割快捷键
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"

# 窗格切换(vim 方向键风格)
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# 窗格大小调整
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5

# 复制模式(vi 风格)
setw -g mode-keys vi
bind Enter copy-mode
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel

# 快捷操作
bind r source-file ~/.tmux.conf \; display "Config reloaded!"
bind x kill-pane
bind X kill-window

The color scheme uses a Catppuccin Mocha-style dark palette (#1e1e2e background, #89b4fa blue highlight), with a status bar that concisely shows the session name and current time.

A few key choices:

  • The prefix key is changed to Ctrl+a, which is much easier to press than the default Ctrl+b (you can pick one that suits your habits)
  • If you nest a local tmux inside a server tmux, it's best to use different prefixes on each side
  • Pane indexing starts at 1, aligning with the numeric keypad
  • Copy mode uses vi keybindings, keeping habits consistent with Neovim

4. Editor — Neovim

There are quite a few text editors to choose from. For simple use cases, you can go with nano; some people also use nvim or vim.

  • People who use nano are generally doing simple text editing in the terminal and won't be doing complex code editing, so they don't need much in the way of text-editing optimization. Nano's shortcut hints are also fairly friendly and easy to use.
  • If you need to do complex editing, Vscode is without a doubt the easiest to get started with and the most practical tool. Its community plugin coverage is comprehensive, it's trusted by users worldwide, and it just works.
  • btw, if you want to tinker a bit or do some geek play, vim-style editors are a good choice. Both vim and nvim are solid options, and you can also go with an out-of-the-box editing config like Lazyvim. Once you get used to vim, it really does improve coding efficiency. But given the current prevalence of vibe coding, compared with efficiently typing out large blocks of code, what we seem to need more is a good terminal code browser (if you're using agent tools like claude code in the terminal and don't want to open another Vscode window). In that case, nvim is also a good choice.

Installation and Initialization

1
brew install neovim

kickstart.nvim is not a distribution; it's a teaching-oriented starting configuration. Its biggest advantage is that you can understand every line of the config — read it from top to bottom starting at init.lua, and you'll know what your editor is doing.

I first forked a copy to my own GitHub, then cloned it locally as the nvim config directory:

1
git clone git@github.com:kzzzza/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim

To manage everything uniformly with chezmoi, I removed the unneeded repository files (.git*, doc/, LICENSE, README), keeping only the core configuration:

1
2
3
4
5
6
7
.config/nvim/
├── .stylua.toml # Lua 格式化配置
├── init.lua # 核心配置文件
├── lazy-lock.json # lazy.nvim 插件版本锁定
└── lua/
├── custom/plugins/ # 自定义插件
└── kickstart/ # kickstart 基础插件配置

Plugin System

I use lazy.nvim as the plugin manager. Below are the selection rationale and core configuration for each plugin:

Basic editor experience:

Plugin Purpose Selection rationale
tokyonight.nvim Color scheme Eye-friendly dark theme, disables italic comments, uses the tokyonight-night variant
which-key.nvim Keybinding hints A lifesaver when you can't remember shortcuts
mini.nvim Statusline / text objects / surround editing Lightweight replacement, one package solves three needs
gitsigns.nvim Git change markers Shows additions/deletions/modifications in the line-number gutter, supports hunk jumping
todo-comments.nvim TODO comment highlighting Highlights TODO, FIX markers in code

Code intelligence:

Plugin Purpose
nvim-lspconfig + mason.nvim LSP configuration and automatic installation
blink.cmp Autocompletion (fuzzy matching implemented in Rust)
conform.nvim Code formatting (runs automatically on save)
nvim-treesitter Syntax highlighting and structural understanding

Navigation and search:

Plugin Purpose
telescope.nvim Fuzzy finding (files, symbols, grep, help docs)
guess-indent.nvim Automatic indentation detection

Snippet:

  • LuaSnip as the snippet engine, used together with blink.cmp

A Few Configuration Details

Some key settings in init.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- leader 键设为空格
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- 启用 Nerd Font 图标
vim.g.have_nerd_font = true

-- 行号 + 相对行号
vim.o.number = true
vim.o.relativenumber = true

-- 系统剪贴板同步
vim.o.clipboard = "unnamedplus"

-- 智能大小写搜索
vim.o.ignorecase = true
vim.o.smartcase = true

-- 实时预览替换
vim.o.inccommand = "split"

Keep 10 lines above and below the cursor (scrolloff = 10), enable mouse mode for convenient split-window dragging, and disable the arrow keys to force myself to use hjkl (although I still occasionally press the wrong keys).

Formatting uses conform.nvim, which runs automatically on save. Currently only Lua is configured (using stylua); I'll add the corresponding formatter when I add Python/Rust later.

5. Command-Line Tools

In addition to the major components above, there are also some small tools that are used frequently on a daily basis:

1
2
3
4
5
brew install ripgrep   # grep 的 Rust 替代,速度飞起
brew install fd # find 的 Rust 替代,语法更友好
brew install bat # cat 的替代,语法高亮 + 行号
brew install eza # ls 的替代,色彩 + 图标
brew install rtk # Claude Code 的 Token 优化代理

6. TODO

There are still a few additions I want to make to this setup. If they don't get done, it means I was focused on the project and didn't spend more time tinkering with the workflow (doge).

  • Linux adaptation: The chezmoi configuration is currently macOS-oriented, with no differentiated setup for personal Linux PCs or servers yet. chezmoi itself supports generating different content based on system variables, so I'll need to use template syntax to separate the Mac and Linux branches later.
  • One-click install script: Write a install.sh by hand so that running it once on a new machine installs all dependencies (Homebrew → chezmoi init → chezmoi apply → install fonts and toolchain). Ideally, switching computers would only require git clone + one ./install.sh.
  • Secret management: chezmoi can encrypt configuration files containing private information such as API keys. I'll explore this later — encrypting private data like model API endpoints or server addresses and managing them uniformly through chezmoi as well.
  • Making the dotfiles repo public: The chezmoi repo is still private for now. Once sensitive information is completely stripped out (confirming no hardcoded API keys or tokens), I plan to make the full dotfiles public on GitHub so others can reference them too.