dotfiles
Personal dotfiles, managed with GNU Stow. Each chapter in the sidebar covers one stow package — what the tool is, what’s been changed from the defaults, and the everyday commands worth knowing.
How this book is built
The book is generated from the per-package READMEs in the repo. Every chapter
is a thin wrapper that pulls in the matching README.md via mdBook’s
{{#include}} directive — so the GitHub repo and this site never drift.
If you want install instructions (Debian / macOS), the modern-CLI tour (zoxide, eza, dust, hyperfine, etc.), or the full subdirectory table, those live in the repo README on GitHub.
Conventions
- Every top-level directory in the repo is a stow package mirroring the
layout it should have under
$HOME. For example,zsh/.zshrcis symlinked to~/.zshrcby runningstow zshfrom the repo root. - The same
bootstrap-debian.shscript handles a fresh Debian/Ubuntu box;brew bundlecovers macOS. - Repo-wide conventions for editing live in
CLAUDE.md.
Quick install
git clone https://github.com/jacob-delgado/dotfiles ~/dotfiles
cd ~/dotfiles
# Debian / Ubuntu
./bootstrap-debian.sh
# macOS
brew bundle && stow zsh vim tmux fzf # pick whichever packages you want
atuin
Stow package for ~/.config/atuin/config.toml. Atuin
is a SQLite-backed shell history with full-text search, per-directory
context, and optional E2E-encrypted sync.
Table of contents
Layout
| File | Stows to |
|---|---|
atuin/.config/atuin/config.toml | ~/.config/atuin/config.toml |
Active settings
enter_accept = true # Enter runs the selected line immediately
search_mode = "fuzzy" # fuzzy match (explicit; matches modern default)
filter_mode = "global" # Ctrl+R searches across all sessions/hosts
workspaces = true # treat git repos as a scope for filter_mode = "workspace"
style = "compact" # denser TUI layout
[sync]
records = true # newer record-based sync protocol
The rest of the file is atuin’s commented default template, kept around as a cheat-sheet of tunable options.
enter_accept = true gotcha: the default behavior is Enter inserts
the line for editing, Tab runs it. This config flips them so Enter runs
the selection immediately — faster but easier to misfire on a dangerous
command. Flip back if that bites you.
Why the file is so long
When atuin first generates config.toml it writes the entire schema
with every option commented out. We keep the full file rather than
trimming to the two active lines so the comments stay around as a
cheat-sheet for what’s tunable.
Regenerating
If you change settings via the atuin TUI (some are configurable there), atuin rewrites this file. Diff it against the tracked version:
git -C ~/dotfiles diff atuin/.config/atuin/config.toml
git -C ~/dotfiles commit -am "Update atuin config"
If atuin’s config schema gains new fields in a future release, you may see a flood of diff noise from the new commented entries — review and commit if you want the updated template.
Fresh-machine setup
brew install atuin # in the Brewfile
stow atuin
atuin import auto # one-time: backfill existing zsh history into atuin's DB
Run atuin register if you want sync across machines; otherwise
everything stays local.
zsh/.zshrc initializes atuin via eval "$(atuin init zsh)".
bat
Stow package for ~/.config/bat/config. bat
is a cat clone with syntax highlighting, line numbers, and git change
markers.
Table of contents
Layout
| File | Stows to |
|---|---|
bat/.config/bat/config | ~/.config/bat/config |
Settings
--theme="ansi" # terminal palette (themed by p10k)
--style="numbers,changes,header,grid,header-filesize" # line nums, git markers, file header + size, gutter grid
--pager="less -RFX" # raw colors, quit-if-one-screen, no alt-screen
--theme="ansi" is intentional — it picks up whatever color scheme the
terminal/Dracula gives us instead of locking to a fixed bat theme. Run
bat --list-themes to see alternatives.
BAT_THEME=ansi is also set in zsh/.zshrc as a belt-and-suspenders
default for tools that invoke bat via env (e.g., MANPAGER).
--pager="less -RFX" matches LESS='-RFX' in zsh/.zshrc so the pager
behaves identically whether bat invokes it or LESS is exported.
Syntax mappings
bat recognizes most filenames by extension/shebang, but a few common
files need help:
--map-syntax="Dockerfile.*:Dockerfile" # e.g. Dockerfile.dev
--map-syntax="*.jenkinsfile:Groovy"
--map-syntax="*.tfvars:HCL" # Terraform variable files
--map-syntax=".envrc:Bash" # direnv files
--map-syntax=".ignore:Git Ignore" # ripgrep / fd ignore files
Add more as new conventions show up.
Regenerating the default template
bat ships a fully commented default config:
bat --generate-config-file # writes ~/.config/bat/config if missing
If you want to start over with the full template, delete the symlink and
run the above, then move the new file into bat/.config/bat/config and
re-stow.
Fresh-machine setup
brew install bat # in the Brewfile
stow bat
btop
Stow package for ~/.config/btop/btop.conf. btop is a process / CPU /
memory / network monitor.
Table of contents
Layout
| File | Stows to |
|---|---|
btop/.config/btop/btop.conf | ~/.config/btop/btop.conf |
Settings
color_theme = "dracula" # matches git/, lazygit/, overall palette
theme_background = False # transparent — inherit terminal bg
truecolor = True # 24-bit color (enabled by tmux too)
graph_symbol = "braille" # denser/prettier than block glyphs
vim_keys = True # hjkl navigation
Cycle themes inside btop with p (uppercase P reverses). Press ?
for full keybindings. Other built-in themes worth trying:
gruvbox_dark, gruvbox_dark_v2, gruvbox_material_dark, nord,
tokyo-night, tokyo-storm.
Churn warning
btop rewrites this file every time you change a setting via its TUI (theme cycling, sort order, etc.). Diff before committing:
git -C ~/dotfiles diff btop/.config/btop/btop.conf
Fresh-machine setup
brew install btop # in the Brewfile
stow btop
direnv
Stow package for ~/.config/direnv/direnvrc. Per-user direnv setup —
.envrc files in your projects can call any of the helpers defined
here.
Table of contents
- Layout
- What direnv is
- Helpers defined here
- Example .envrc files
- nix-direnv integration
- Fresh-machine setup
Layout
| File | Stows to |
|---|---|
direnv/.config/direnv/direnvrc | ~/.config/direnv/direnvrc |
What direnv is
direnv hooks into your shell so that whenever you cd into a directory
containing .envrc, it loads that file as environment for the directory
(and unloads on leaving). Activated via the OMZ direnv plugin in
zsh/.zshrc. Trust new .envrc files explicitly with direnv allow.
Helpers defined here
| Helper | Usage in .envrc | What |
|---|---|---|
layout python_venv [python] | layout python_venv python3.12 | create/activate ./.venv/; pin python binary |
layout uv | layout uv | activate ./.venv/ created by uv venv (faster pip-replacement) |
use go <version> | use go 1.21 | use Homebrew’s go@1.21 (run brew install go@1.21 first) |
use node <version> | use node 20 | use Homebrew’s node@20 |
op_env <item> | op_env "GitHub PAT" | inject env vars from a 1Password item (needs op signed in) |
use_flake / use_nix | (provided by nix-direnv when sourced) | activate a nix flake / shell |
Example .envrc files
Python project with uv:
layout uv
export PYTHONDONTWRITEBYTECODE=1
Go service with a version pin:
use go 1.22
export CGO_ENABLED=0
PATH_add bin
Project that needs secrets from 1Password:
op_env "production-postgres" # injects DATABASE_URL, etc.
Then direnv allow once and the env loads automatically when you cd
in.
nix-direnv integration
If nix-direnv is installed (Mac: brew install nix-direnv; Linux:
distro package or via nix itself), the direnvrc sources it
automatically — use_flake / use_nix become available to .envrc
files. Without nix-direnv installed, the source-attempt is silent.
Fresh-machine setup
brew install direnv # in the Brewfile
stow direnv
# OMZ's direnv plugin (in zsh/.zshrc) hooks into the shell on startup.
editorconfig
Stow package for ~/.editorconfig. Vim picks it up via
editorconfig/editorconfig-vim (in vim/.vimrc); most editors / IDEs
support it natively.
Table of contents
Layout
| File | Stows to |
|---|---|
editorconfig/.editorconfig | ~/.editorconfig |
Defaults
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100
Per-language overrides:
| Pattern | Indent | Notes |
|---|---|---|
*.go | tab | Go convention |
Makefile, *.mk | tab | Make requires tabs |
*.py | 4 spaces | PEP 8 |
*.{rs,c,cpp,h,hpp,java,kt,scala} | 4 spaces | |
*.md | — | trim_trailing_whitespace = false, no max_line_length |
Per-project overrides
This file has root = true so editorconfig stops walking up the
filesystem when it finds it. Any project’s own .editorconfig
overrides this one — drop a more specific file in your project root
when you need different rules.
Fresh-machine setup
stow editorconfig
Vim’s editorconfig-vim plugin (in vim/) reads this automatically.
For other editors: VS Code, JetBrains, Sublime, Neovim, and most modern
editors support .editorconfig natively or via a free plugin.
fzf
Stow package for ~/.fzf.zsh. Sourced automatically by the oh-my-zsh
fzf plugin on shell startup.
Table of contents
- Defaults
- Colors (Dracula)
- fd integration
- Keybinding-specific options
- frg — live ripgrep + fzf
- Fresh-machine setup
Defaults
FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
FZF_TMUX=1
FZF_TMUX=1 makes fzf pop up in a tmux popup pane when invoked inside
tmux — much nicer than taking over the whole terminal.
Colors (Dracula)
FZF_DEFAULT_OPTS also carries the official Dracula
--color palette so the picker matches kitty/tmux/vim:
--color=fg:#f8f8f2,bg:#282a36,hl:#bd93f9
--color=fg+:#f8f8f2,bg+:#44475a,hl+:#bd93f9
--color=info:#ffb86c,prompt:#50fa7b,pointer:#ff79c6
--color=marker:#ff79c6,spinner:#ffb86c,header:#6272a4
Unlike bat/tig (which inherit the terminal’s ANSI palette), fzf’s
colors are pinned to Dracula hex here.
fd integration
If fd is on PATH, replace fzf’s built-in walker with it. fd is
faster than the default find invocation and respects .gitignore:
if command -v fd >/dev/null; then
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND='fd --type d --hidden --follow --exclude .git'
fi
Guarded so machines without fd still get a working fzf.
Keybinding-specific options
OMZ’s fzf plugin binds:
| Keys | Action | Options set here |
|---|---|---|
Ctrl+T | insert a selected file path into the command line | FZF_CTRL_T_OPTS — bat preview, falling back to cat |
Ctrl+R | history search | FZF_CTRL_R_OPTS — 3-line wrapped preview pane |
Alt+C | cd to a selected directory | FZF_ALT_C_COMMAND — fd-based directory walker (above) |
export FZF_CTRL_T_OPTS="--preview 'bat --color=always --style=numbers --line-range :500 {} 2>/dev/null || cat {}'"
export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:wrap"
frg — live ripgrep + fzf
A function for project-wide content search. Type to refine; rg re-runs on
every keystroke. Enter opens the chosen line in $EDITOR.
frg # start with an empty query, type to search
frg "TODO" # seed the query
Implementation:
frg() {
local rg_prefix='rg --column --line-number --no-heading --color=always --smart-case'
fzf --ansi --disabled --query "${*:-}" \
--bind "start:reload:$rg_prefix {q} || true" \
--bind "change:reload:sleep 0.1; $rg_prefix {q} || true" \
--delimiter : \
--preview 'bat --color=always --style=numbers --highlight-line {2} -- {1}' \
--preview-window 'right,60%,border-left,+{2}+3/3,~3' \
--bind "enter:become(${EDITOR:-vim} +{2} {1})"
}
Also bound to prefix + g in tmux — see tmux/README.md.
Fresh-machine setup
stow fzf # symlinks ~/.fzf.zsh
The OMZ fzf plugin (in zsh/.zshrc’s plugins list) auto-sources the
symlink. No manual source line needed.
gh
Stow package for ~/.config/gh/config.yml only. The GitHub CLI’s
companion file hosts.yml is deliberately not tracked — it carries
OAuth tokens.
Table of contents
Layout
| File | Stows to | Tracked? |
|---|---|---|
gh/.config/gh/config.yml | ~/.config/gh/config.yml | yes |
(~/.config/gh/hosts.yml) | — | no (gitignored) |
Active customization
prefer_editor_prompt: disabled # prompt inline in the terminal, not in $EDITOR
pager: delta # syntax-highlight `gh pr diff` and similar
aliases:
co: pr checkout
prs: pr list --author "@me"
prv: pr view --web
prc: pr create --fill --web
issues: issue list --assignee "@me"
watch: run watch
compare: '!gh repo view --web --branch "$(git branch --show-current)"'
Alias quick-ref:
| Alias | Expands to | Use |
|---|---|---|
gh co <#> | gh pr checkout <#> | check out a PR locally |
gh prs | gh pr list --author "@me" | list my open PRs in this repo |
gh prv | gh pr view --web | open this branch’s PR in browser |
gh prc | gh pr create --fill --web | new PR from commits, open in browser |
gh issues | gh issue list --assignee "@me" | issues assigned to me |
gh watch <run-id> | gh run watch <run-id> | tail a GH Action run |
gh compare | shells out — opens the GH compare view for the current branch |
The leading ! on compare is a shell-out alias; everything after runs
in a subshell.
Extensions
Installed via gh extension install; they live in
~/.local/share/gh/extensions/ (not in this repo). List with
gh extension list, update with gh extension upgrade --all.
| Extension | Command | Use |
|---|---|---|
dlvhdr/gh-dash | gh dash | TUI dashboard of PRs/issues across repos with filters |
seachicken/gh-poi | gh poi | prune local branches whose PRs have been merged |
kyanny/gh-pr-draft | gh pr-draft | toggle the draft state of a PR |
Reinstall on a fresh machine:
gh extension install dlvhdr/gh-dash
gh extension install seachicken/gh-poi
gh extension install kyanny/gh-pr-draft
Why hosts.yml stays out
~/.config/gh/hosts.yml is where gh stores per-host auth state. On
machines using token auth (Linux/CI) it contains the literal token; on
macOS gh typically uses Keychain but the file still holds the user
identity. Either way, it’s per-machine state, not config, so:
gh/.config/gh/hosts.ymlis listed in the repo root.gitignore.- The file lives at
~/.config/gh/hosts.ymlas a regular file (not a symlink) andstow ghleaves it alone.
If you ever see hosts.yml show up in git status, something has gone
wrong — investigate before committing.
Schema churn warning
gh rewrites config.yml when new schema fields are added in a release
(it adds new commented-out entries). Expect occasional noisy diffs after
brew upgrade gh. Either commit the new schema or git checkout to
keep the previous version.
Fresh-machine setup
brew install gh # in the Brewfile
stow gh
gh auth login # populates hosts.yml (which stays untracked)
git
Stow package for ~/.gitconfig.
Table of contents
- Layout
- Local / private overrides
- Delta integration
- Behavior tuning
- Aliases
- Dracula color scheme
- URL aliases
- Companion tools
- Fresh-machine setup
Layout
| File | Stows to |
|---|---|
git/.gitconfig | ~/.gitconfig |
Local / private overrides
The tracked .gitconfig ends with:
[include]
path = ~/.gitconfig.local
Anything machine-specific or sensitive (work email, signing keys, work
remotes, credential helpers) lives in ~/.gitconfig.local, which is
not stowed and not in the repo. Create it by hand per machine.
For example, gh auth setup-git writes a [credential] helper with a
host-specific path (/opt/homebrew/bin/gh on macOS) — that belongs here,
not in the tracked .gitconfig.
Delta integration
git-delta is wired in as the pager
for git diff, show, log, blame:
[core]
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true # n / N jumps between hunks
side-by-side = true # split view (drop for narrow terminals)
line-numbers = true
[merge]
conflictstyle = zdiff3 # better 3-way conflict markers; needs git ≥ 2.35
brew install git-delta provides the binary (already in the Brewfile).
Behavior tuning
[init] defaultBranch = main
[pull] rebase = true; ff = only # rebase on pull, error on divergence
[push] default = current; autoSetupRemote = true; followTags = true
[fetch] prune = true # drop refs to deleted remote branches
[rebase] autosquash = true; autoStash = true # fixup! commits flow; uncommitted work stashes
[diff] algorithm = histogram; colorMoved = default; renames = copies
[rerere] enabled = true # remember conflict resolutions
These set sane defaults for git’s nagging warnings (divergent branches,
missing upstream) and unlock features that aren’t on by default but
should be (rerere, histogram diff, move-block detection).
Aliases
| Alias | Expands to | Use |
|---|---|---|
git st | status -sb | short status with branch line |
git co | checkout | |
git br | branch | |
git cm | commit | |
git ca | commit --amend | |
git cane | commit --amend --no-edit | amend without re-editing the message |
git lg | pretty-graph log | one-line graph with colors and ago-style dates |
git last | log -1 HEAD --stat | what did the last commit touch? |
git staged | diff --cached | review what’s about to be committed |
git unstage | reset HEAD -- | undo git add |
git undo | reset --soft HEAD^ | un-commit (keeping changes) |
git wip | add -A && commit -m 'WIP' | snapshot to step away |
git prune-branches | delete every merged branch (except current / main / master) | safe local cleanup |
git cp | cherry-pick | |
git sw / git swc | switch / switch -c | modern branch switch / create |
git cob | checkout -b | create + switch (older style) |
git aa / git ap | add -A / add -p | stage all / interactively by hunk |
git fa | fetch --all --prune | refresh all remotes, drop stale branches |
git pf | push --force-with-lease | safe force-push (won’t clobber others’ work) |
git rbi | rebase -i --autosquash | interactive rebase, auto-orders fixups |
git rbc / git rba | rebase --continue / --abort | |
git fixup | commit --fixup <sha> | marks a fixup commit for rbi to squash |
git recent | last 10 branches by commit date | |
git root | rev-parse --show-toplevel | print the repo root |
git aliases | list every configured alias |
Dracula color scheme
The [color "*"] sections set a Dracula-inspired palette for branches,
diffs, grep, interactive prompts, and status. Most slots are explicit;
empty values mean “use the default.”
URL aliases
[url "https://github.com/dracula/"]
insteadOf = dracula://
Lets you git clone dracula://vim instead of typing the full URL.
Companion tools
Installed via Brewfile, no extra config required:
| Tool | Use |
|---|---|
git-delta | pager (wired in above) |
git-absorb | git absorb — auto-creates fixup! commits targeting the right earlier commit based on what lines your unstaged changes touch. Pair with git rebase -i --autosquash (which [rebase] autosquash = true runs by default). |
git-branchless | adds porcelain inspired by Mercurial/Sapling: git smartlog, git move, git restack, git undo (cross-operation undo). Power-user; read git help branchless first. |
Fresh-machine setup
stow git
Create ~/.gitconfig.local for any per-machine bits (work email, signing
keys, etc.) that shouldn’t live in the repo. git-delta, git-absorb,
and git-branchless come in via the Brewfile.
gitignore_global
Stow package for ~/.gitignore_global. Wired into git via
[core] excludesfile = ~/.gitignore_global in git/.gitconfig.
Table of contents
Layout
| File | Stows to |
|---|---|
gitignore_global/.gitignore_global | ~/.gitignore_global |
What’s in it
OS, editor, and tool noise that shouldn’t appear in any project:
- macOS:
.DS_Store,._*,.Spotlight-V100,.fseventsd, … - Linux:
.directory,.Trash-*,.nfs* - Windows:
Thumbs.db,Desktop.ini - Vim:
*.swp,*~,.netrwhist - IDEs:
.idea/,.vscode/,*.sublime-workspace,.history/ - ctags / LSP caches:
tags,.ccls-cache/ - direnv:
.direnv/ - pre-commit / lefthook caches
- Generic:
*.bak,*.orig,*.rej
What’s not in it
Per-project artifacts (node_modules/, __pycache__/, dist/,
target/, *.pyc, build dirs, etc.) stay in each project’s own
.gitignore. A global ignore for those would mask bugs in repos that
should be excluding them deliberately.
Fresh-machine setup
stow gitignore_global
git config --global core.excludesfile ~/.gitignore_global # already in git/.gitconfig
hadolint
Stow package for ~/.config/hadolint/config.yaml. hadolint is a
Dockerfile linter. Used by lefthook on pre-commit and by hand.
Table of contents
Layout
| File | Stows to |
|---|---|
hadolint/.config/hadolint/config.yaml | ~/.config/hadolint/config.yaml |
Settings
Ignored rules:
| Code | Rule | Why ignored |
|---|---|---|
DL3008 | apt-get without version pin | unpinned apt is fine in scratch/dev images |
DL3018 | apk add without version pin | same logic for Alpine |
DL3015 | apt-get without --no-install-recommends | stylistic preference |
Trusted registries (skip “use a versioned tag” warnings for these):
docker.io, gcr.io, quay.io, ghcr.io, registry.k8s.io,
public.ecr.aws.
failure-threshold: warning makes hadolint exit non-zero only for
warning/error rules — informational findings don’t fail builds.
Per-project overrides
A repo can drop a .hadolint.yaml at the root; it wins. Per-line
disables also work:
# hadolint ignore=DL3007
FROM nginx:latest
Fresh-machine setup
brew install hadolint # in the Brewfile
stow hadolint
i3
Stow package for i3wm and i3status configs. Linux/X11 only — not stowed by default on macOS.
| Source | Target |
|---|---|
i3/.config/i3/config | ~/.config/i3/config |
i3/.config/i3status/config | ~/.config/i3status/config |
Table of contents
i3wm config
Started from i3-config-wizard defaults; deviations called out below.
Mod key
set $mod Mod4 — i.e., the Super (Windows/Command) key. floating_modifier $mod lets you Mod-drag floating windows.
Navigation
Vim-style:
| Keys | Action |
|---|---|
Mod+h/j/k/l | focus left/down/up/right |
Mod+Shift+h/j/k/l | move focused window |
Mod+Left/Down/Up/Right | same as above (arrow alternatives) |
Mod+space | toggle focus between tiling and floating |
Mod+Shift+space | toggle the focused window’s floating state |
Mod+a | focus parent container |
Layouts and splits
| Keys | Action |
|---|---|
Mod+g | split horizontally (next window opens to the right) |
Mod+v | split vertically |
Mod+z | toggle fullscreen for focused container |
Mod+s / Mod+w / Mod+e | stacked / tabbed / split (toggle) layout |
Mod+g is the non-default — i3’s wizard uses Mod+h for horizontal
split, which conflicts with the focus binding above. This config remaps
horizontal-split to g.
Workspaces
Mod+1..0 switch; Mod+Shift+1..0 move the focused container to that
workspace. Standard.
| Keys | Action |
|---|---|
Mod+Shift+c | reload i3 config |
Mod+Shift+r | restart i3 in place (keeps session) |
Mod+Shift+e | exit i3 (i3-msg exit) |
Mod+Shift+q | kill focused window |
Mod+Return | open i3-sensible-terminal |
Mod+d | dmenu_run |
Multimedia / system keys
| Key | Action |
|---|---|
XF86AudioLowerVolume | amixer set Master 8%- |
XF86AudioRaiseVolume | amixer set Master 8%+ |
XF86AudioMute | amixer set Master toggle |
XF86AudioPrev/Next/Play | mpc prev / mpc next / mpc_toggle |
XF86MonBrightnessUp/Down | xbacklight -inc/-dec 10 |
(These assume ALSA + mpd + xbacklight are installed.)
Modes
Mod+renters resize mode —h/j/k/lshrink/grow by 10px;EnterorEscto exit.Caps_LockandNum_Lockenter named modes solely so i3 displays a visual indicator on the bar when those locks are on. Pressing the same key again exits.
Autostart
Run by i3 on session start:
exec --no-startup-id start-pulseaudio-x11
exec --no-startup-id syndaemon -t -k -i 2 -d # disable touchpad while typing
exec --no-startup-id feh --bg-scale '/home/jacob/wallpapers/arch1080.jpg'
exec --no-startup-id unclutter -root -visible # hide idle cursor
exec --no-startup-id dunst # notification daemon
exec --no-startup-id xrandr --dpi 165 # HiDPI
Note the hardcoded wallpaper path under /home/jacob/ — update for the
host or replace with a relative path.
The status bar runs i3status:
bar { status_command i3status }
focus_follows_mouse no — focus only on click, not on hover.
Theme (Dracula)
The official Dracula palette, matching the rest of the dotfiles:
client.*window-border colors (focused#6272A4, urgent#FF5555, unfocused#282A36).bar { colors { … } }for the i3bar (background#282A36, statusline#F8F8F2, urgent#FF5555).- dmenu launch colors on
Mod+d(-nb #282A36 -sb #6272A4 -sf #F8F8F2). - i3status
color_good/color_degraded/color_bad=#50FA7B/#F1FA8C/#FF5555(see below).
i3status config
Modules in display order:
wireless wlp1s0 # SSID + signal + IP
battery 0 # percentage; charging icon ⚡, discharging ⚇, full ☻
load # 1-min load avg
cpu_usage # %
volume master # ALSA Master percentage
tztime local # %I:%M%p %b %d %Y
Refresh interval: 5 seconds. Battery threshold for “low” warning: 10%.
Battery path is hard-coded to /sys/class/power_supply/BAT%d/uevent
(%d is replaced by the battery number).
Wireless interface is hard-coded to wlp1s0. Adjust per machine.
Commented-out modules: ipv6, disk /, disk /home, DHCP run-watch,
VPNC pidfile, VPN tun0 path-exists check.
Fresh-machine setup
apt install i3 i3status dmenu feh dunst # plus pulseaudio, alsa, mpd, xbacklight as needed
stow i3 # symlinks ~/.config/i3/ and ~/.config/i3status/
bootstrap-debian.sh does not stow this by default — only if the
machine actually runs an X session.
kitty
Stow package for ~/.config/kitty/. Kitty
is the GPU-accelerated terminal — used here on macOS (/Applications/kitty.app,
installed via the kitty.sh installer, not Homebrew).
Table of contents
Layout
| File | Stows to |
|---|---|
kitty/.config/kitty/kitty.conf | ~/.config/kitty/kitty.conf |
kitty/.config/kitty/dracula.conf | ~/.config/kitty/dracula.conf |
kitty/.config/kitty/diff.conf | ~/.config/kitty/diff.conf |
Reload after editing: kitty @ load-config (needs allow_remote_control yes),
or Cmd+Ctrl+, (macOS) / Ctrl+Shift+F5 (Linux).
Main config
kitty.conf pulls in the Dracula palette, sets the font, and tunes a
handful of defaults. Sections:
Font — JetBrainsMono Nerd Font Mono is the patched build with Nerd
Font glyphs (install via the font-jetbrains-mono-nerd-font cask, or
grab from nerdfonts.com).
Quality of life
| Setting | Why |
|---|---|
enable_audio_bell no / visual_bell_duration 0 | Silence the bell entirely |
confirm_os_window_close 0 | Don’t prompt when closing a window with a running process |
scrollback_lines 10000 | Bigger in-memory scrollback |
scrollback_pager_history_size 100 | 100 MB disk-backed scrollback for the pager kitten |
repaint_delay 8 / input_delay 2 / sync_to_monitor yes | Snappier rendering on Apple Silicon |
macOS
| Setting | Why |
|---|---|
macos_option_as_alt left | Left-Option behaves as Alt so ⌥←/⌥→ word-jump works in shells |
macos_titlebar_color background | Title bar adopts the bg color (no white strip) |
macos_quit_when_last_window_closed yes | Quitting the last window quits the app |
Remote control + shell integration
| Setting | Why |
|---|---|
allow_remote_control yes | Unlocks kitty @ ls, kitty @ set-tab-title, the ssh kitten, etc. |
listen_on unix:/tmp/kitty-{kitty_pid} | Per-process control socket |
shell_integration enabled | Click-to-move cursor, prompt marking, jump-by-prompt, etc. |
Visual
| Setting | Why |
|---|---|
window_padding_width 6 | Subtle gutter between text and window edge |
tab_bar_edge top | Tabs on top, away from prompt output |
tab_bar_style powerline + tab_powerline_style slanted | Curved powerline separators (uses JetBrainsMono Nerd Font glyphs) |
Dracula theme
dracula.conf is the upstream palette from
https://draculatheme.com/kitty, unmodified. It sets:
- Foreground/background and the full ANSI palette (
color0–color15) - Cursor, selection, URL underline
- Tab bar foreground/background (active + inactive)
- Split/window border colors
- Mark colors (
kitty’s scrollback marks feature)
Matches the rest of the dotfiles’ Dracula-ish palette ([[../tig/README.md]], [[../lazygit/README.md]]).
Diff colors
diff.conf styles kitty +kitten diff (kitty’s built-in side-by-side
diff viewer) with the Dracula palette — green for added, red for removed,
purple hunk separators, cyan search, yellow selection.
Invoke with kitty +kitten diff old new or alias it.
Fresh-machine setup
# kitty itself: NOT in Brewfile — installed standalone on macOS via
# curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin
# On Debian: apt install kitty (or use the same installer).
stow kitty
lazygit
Stow package for ~/.config/lazygit/config.yml.
Table of contents
Layout
| File | Stows to |
|---|---|
lazygit/.config/lazygit/config.yml | ~/.config/lazygit/config.yml |
Find the live config dir on any machine with lazygit --config-dir.
Theme
The official Dracula theme (copied
verbatim from upstream), matching git/.gitconfig:
| Element | Color |
|---|---|
| activeBorderColor | #FF79C6 bold (pink) |
| inactiveBorderColor | #BD93F9 (purple) |
| searchingActiveBorderColor | #8BE9FD bold (cyan) |
| selectedLineBgColor | #6272A4 (comment) |
| optionsTextColor | #6272A4 (comment) |
| unstagedChangesColor | #FF5555 (red) |
| defaultFgColor | #F8F8F2 (foreground) |
Plus cherry-pick / marked-base-commit colors — see the file for the full upstream block.
Other settings
gui:
nerdFontsVersion: "3" # use NF v3 glyphs (font-hack-nerd-font in Brewfile)
showRandomTip: false # silence the splash tips
showCommandLog: false # hide the bottom command-log panel
showFileTree: true # tree view in the files panel
experimentalShowBranchHeads: true # show branch heads in the commit log
git:
paging:
colorArg: always
pager: delta --paging=never # diff pane uses delta, matching `git diff` in the terminal
os:
editPreset: "vim" # `e` opens files in vim
The delta pager means lazygit’s diff pane renders with the same
side-by-side, syntax-highlighted view you see from git diff. Both
read settings from git/.gitconfig’s [delta] block.
gh integration (custom commands)
| Key | Context | Action |
|---|---|---|
O | global | gh pr view --web — open this branch’s PR in browser |
C | global | gh pr create --fill --web — create PR for current branch |
V | commits | gh pr view --web {SHA} — open PR for selected commit |
L | global | gh pr list --author "@me" — list my PRs (terminal output) |
X | global | gh poi — prune branches whose PRs are merged (terminal output) |
{{.SelectedLocalCommit.Sha}} is lazygit’s template syntax for the
selected entry in the commits panel.
Schema reference
Full options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md
Keep this file lean — lazygit can write to it when settings change via its UI, which creates noisy diffs when you only meant to tweak one thing.
Fresh-machine setup
brew install lazygit # in the Brewfile
stow lazygit
lefthook
Per-project git-hooks manager template. Lefthook has no global config
— this directory ships a lefthook.yml you can copy or extends: into
new repos.
Table of contents
- Layout
- Two ways to use it
- What the template runs
- Adding repo-specific hooks
- Skipping a hook
- Fresh-machine setup
Layout
| File | Purpose |
|---|---|
lefthook/lefthook.yml | Go-dev baseline template (no symlink) |
This package is not stowed — lefthook.yml is meant to live inside
your project repos, not in $HOME.
Two ways to use it
1. Copy into a new repo
cp ~/dotfiles/lefthook/lefthook.yml ./lefthook.yml
lefthook install
lefthook install writes the .git/hooks/* shims that invoke lefthook.
You only need to do this once per clone.
2. Extend it from your project’s lefthook.yml
# project/lefthook.yml
extends: ~/dotfiles/lefthook/lefthook.yml
pre-commit:
commands:
# project-specific addition
api-spec-check:
glob: "openapi.yaml"
run: spectral lint {staged_files}
Then lefthook install and additions stack on top of the baseline.
What the template runs
pre-commit (parallel):
| Hook | Glob | What |
|---|---|---|
gofmt | *.go | fail if any file isn’t gofmt’d |
goimports | *.go | fail if imports aren’t sorted |
golangci-lint | *.go | run lint, only new findings since HEAD |
govet | *.go | go vet ./... |
shellcheck | *.{sh,bash} | lint staged shell |
shfmt | *.{sh,bash} | format-diff shell (2-space, switch-case indent) |
yamllint | *.{yml,yaml} | YAML lint |
hadolint | Dockerfile* | Dockerfile lint |
commit-msg: reject empty subjects.
pre-push (parallel):
| Hook | Glob | What |
|---|---|---|
go-test | *.go | go test ./... |
go-build | *.go | go build ./... |
All the linters / formatters are in the Brewfile.
Adding repo-specific hooks
The most common pattern: extends: the baseline, then add or override.
Anything you add lives in the project repo (versioned with the code).
Skipping a hook
LEFTHOOK_EXCLUDE=golangci-lint git commit -m "wip"
# or skip all hooks (use sparingly):
git commit --no-verify
Fresh-machine setup
brew install lefthook # in the Brewfile
# In each repo where you want hooks:
lefthook install
p10k
Stow package for ~/.p10k.zsh — the configured
powerlevel10k prompt.
Table of contents
Why this is in the repo
The theme itself ships with sensible defaults, but the prompt I actually
use is a customized version produced by running p10k configure. Without
this file, a fresh machine gets the wizard the first time zsh starts —
which is fine, but I prefer the deployed prompt to match what I’m used
to.
The file is ~1700 lines and mostly auto-generated; treat it as a
snapshot, not as hand-tuned config. Edits are fine, but re-running
p10k configure overwrites them.
How it’s loaded
zsh/.zshrc ends with:
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
Plus the instant-prompt preamble at the top of .zshrc (must stay close
to the top to be effective):
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
The instant-prompt cache file is regenerated automatically as you use the shell; it isn’t in this repo.
Colors (Dracula)
This is a rainbow-style config with the official
Dracula color values merged in
(colors only — segments, layout, and options are mine). The Dracula
theme is itself rainbow-based and mostly uses ANSI slots 0–7, so it
relies on the terminal palette being Dracula (kitty’s dracula.conf)
rather than hardcoding hex; only ~11 segment colors are overridden
(os_icon, dir text, time, status, prompt char, multiline gap).
⚠️ Re-running p10k configure overwrites these. After regenerating,
re-merge the Dracula values — pull files/.p10k.zsh from
dracula/powerlevel10k and
copy across only the *_FOREGROUND/*_BACKGROUND/*_COLOR values, or
re-run the merge script used originally.
Regenerating
If you change the prompt with p10k configure, the wizard writes the new
config to ~/.p10k.zsh — which is symlinked here. Commit the resulting
diff (then re-apply the Dracula colors per the section above):
p10k configure # interactive wizard
git -C ~/dotfiles diff p10k/.p10k.zsh
git -C ~/dotfiles commit -am "Regenerate p10k prompt"
Fresh-machine setup
stow p10k # symlinks ~/.p10k.zsh
Theme itself (cloned as an OMZ custom theme) is installed by
bootstrap-debian.sh:
git clone --depth=1 https://github.com/romkatv/powerlevel10k \
"${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
You’ll want a Nerd Font installed in your terminal for the prompt’s
glyphs to render. The Brewfile pulls in font-hack-nerd-font.
ripgrep
Stow package for ~/.config/ripgrep/config. The config file is only
read if RIPGREP_CONFIG_PATH points at it — rg does not look for it
by default.
Table of contents
Layout
| File | Stows to |
|---|---|
ripgrep/.config/ripgrep/config | ~/.config/ripgrep/config |
Required env var
zsh/.zshrc sets:
export RIPGREP_CONFIG_PATH="$HOME/.config/ripgrep/config"
Without that variable, rg ignores the config file completely.
Settings
--smart-case # case-insensitive unless the query has uppercase
--hidden # search dotfiles (still respects .gitignore)
--max-columns=200 # cap matched-line width
--max-columns-preview # show a truncation message on long lines
Glob excludes (additional to whatever .gitignore says):
--glob=!.git/*
--glob=!node_modules/*
--glob=!.venv/*
--glob=!__pycache__/*
--glob=!dist/*
--glob=!build/*
--glob=!target/* # Rust + JVM build dirs
--glob=!vendor/* # Go vendored deps
--glob=!*.min.js
--glob=!*.min.css
--glob=!*.map # source maps
Custom filetype aliases — rg --type <alias> "pattern":
--type-add=tf:*.{tf,tfvars}
--type-add=k8s:*.{yaml,yml}
--type-add=helm:*.{yaml,yml,tpl}
--type-add=proto:*.proto
--hidden makes rg walk into .config/, .local/, etc. — the
--glob exclusions then prune the noisy ones.
Anything you override on the CLI wins over the config file.
Colors (Dracula)
The official Dracula palette, so rg
output matches fzf/kitty/etc:
--colors=path:fg:0xbd,0x93,0xf9 # purple paths
--colors=line:fg:0x50,0xfa,0x7b # green line numbers
--colors=column:fg:0x50,0xfa,0x7b # green column numbers
--colors=match:fg:0xff,0x55,0x55 # red match highlights
Verifying it’s loaded
rg foo --debug 2>&1 | head -5
The first line of debug output names the config file path being read.
Companion: ripgrep-all (rga)
brew install ripgrep-all provides rga, a wrapper that runs rg
across non-text files by extracting their content first: PDFs, ebooks
(EPUB, MOBI), Office docs, archives (zip, tar, 7z), and even SQLite
databases:
rga "encryption" ~/Documents/papers # grep across PDFs
rga "TODO" project.zip # grep inside an archive without extracting
rga reads its own config dir (~/.config/ripgrep-all/) — it does
not share this ripgrep/config file. Run rga --help to set up
adapter-specific options.
Fresh-machine setup
brew install ripgrep ripgrep-all # both in the Brewfile
stow ripgrep
# RIPGREP_CONFIG_PATH is exported by zsh/.zshrc already
shellcheck
Stow package for ~/.shellcheckrc. shellcheck is the de-facto shell
script linter. Used by lefthook on pre-commit, ALE in vim, and by
hand.
Table of contents
Layout
| File | Stows to |
|---|---|
shellcheck/.shellcheckrc | ~/.shellcheckrc |
What’s enabled / disabled
enable=all
shell=bash
external-sources=true
disable=SC2034 # variable appears unused (false positive in sourced libs)
disable=SC1090 # can't follow non-constant source
disable=SC1091 # can't find source file
The enable=all turns on shellcheck’s optional checks (literal numeric
comparison, etc.) — more signal at the cost of slightly more verbose
output. The three disabled codes are the ones that fire most often on
legitimate code (utility scripts that source siblings, library files
that define helpers their callers use, etc.).
Per-project overrides
Each rule can be re-enabled in a single file via a comment:
#!/usr/bin/env bash
# shellcheck enable=SC2034
Or per-line:
foo=bar # shellcheck disable=SC2034
A repo can also drop its own .shellcheckrc at the root; it wins.
Fresh-machine setup
brew install shellcheck # in the Brewfile
stow shellcheck
tig
Stow package for ~/.tigrc. tig is the text-mode interface for Git —
the read/navigate companion to lazygit’s interactive UI.
Table of contents
Layout
| File | Stows to |
|---|---|
tig/.tigrc | ~/.tigrc |
Display options
set git-colors = no # don't double-style on top of git's color.* output
set diff-options = -m --first-parent # render merges as patches; follow first parent only
set show-changes = yes # staged/unstaged appear as faux commits in main view
set blame-options = -C -C -C # `tig blame` follows copies across files
set wrap-lines = yes # wrap long lines instead of clipping
show-changes = yes is especially nice: in the main commit view, the
top entries show your current HEAD state with unstaged + staged
changes — no need to bounce back to git status.
Color scheme
Dracula-ish, using ANSI color slots (named colors) rather than hex.
Each terminal’s Dracula theme maps the ANSI palette consistently
(magenta → pink, blue → comment-purple, etc.), so the same .tigrc
renders right on macOS iTerm/Terminal.app and Linux URxvt/Wezterm
without per-platform tweaks.
| Element | Color |
|---|---|
| Selected line (cursor) | bold magenta (pink) |
| Search results | black on yellow |
| Line numbers | blue (comment-purple), subtle |
| Active pane title | bold magenta on black |
| Inactive pane title | blue on black |
| diff +/- bodies | green / red |
| diff +/- highlight | inverse green / red |
| Commit headers | bold yellow |
| Index lines | cyan |
| Hunk markers | magenta |
| Author column | cyan |
| Date column | blue (comment-purple) |
| HEAD marker | bold magenta |
| Tags | bold yellow |
| Remote refs | green |
Trailers (Reported-by:, Signed-off-by:) | green |
Key bindings
| Key | Action |
|---|---|
Y | Copy the selected commit SHA to the system clipboard (pbcopy / xclip / wl-copy, whichever is available) |
All of tig’s built-in bindings (Enter view commit, R refresh, q
quit, ? help, etc.) remain unchanged.
Fresh-machine setup
brew install tig # in the Brewfile
stow tig
tmux
Stow package for ~/.tmux.conf. Installs via stow tmux from the dotfiles
root. TPM auto-bootstraps on first run, so a fresh machine just needs tmux
itself.
Table of contents
Quick reference
Prefix is Ctrl+A (not the tmux default Ctrl+B). Everything below
assumes you press the prefix first.
Sessions, windows, panes
| Keys | Action |
|---|---|
prefix + a | Toggle to last window |
prefix + c | New window |
prefix + , | Rename window |
prefix + s | Session picker (built-in) |
prefix + F | fzf picker for sessions / windows / panes (tmux-fzf) |
prefix + g | Ripgrep+fzf popup over the current pane’s directory (runs frg; opens result in $EDITOR) |
prefix + d | Detach session |
prefix + r | Reload ~/.tmux.conf |
Splits and navigation (tmux-pain-control)
| Keys | Action |
|---|---|
| `prefix + | ` |
prefix + - | Split pane vertically (top/bottom) |
prefix + h/j/k/l | Move focus left/down/up/right |
prefix + H/J/K/L | Resize pane (repeatable) |
prefix + < / > | Swap window with previous/next |
Copy / paste (vi mode + tmux-yank)
| Keys | Action |
|---|---|
prefix + [ | Enter copy mode |
v | Begin visual selection |
y | Copy selection to system clipboard (pbcopy / xclip / wl-copy) |
q | Exit copy mode |
prefix + ] | Paste |
Session persistence (tmux-resurrect + tmux-continuum)
| Keys | Action |
|---|---|
prefix + Ctrl-s | Save current session manually |
prefix + Ctrl-r | Restore last saved session |
Continuum also auto-saves every 15 minutes and auto-restores the last
session whenever tmux starts (@continuum-restore 'on').
Misc
| Keys | Action |
|---|---|
prefix + Ctrl-s (this config) | Toggle synchronize-panes for the current window |
| Mouse | Enabled — click to focus panes, drag to resize, scroll to scrollback |
SYNC shows in the right status bar while pane sync is active.
Plugins
Managed by TPM. Install or update with
prefix + I (capital i) inside tmux.
| Plugin | Purpose |
|---|---|
tmux-sensible | Saner defaults (history-limit, escape-time, focus-events, etc.) |
tmux-pain-control | ` |
tmux-yank | Copy from tmux to the OS clipboard |
tmux-resurrect | Save / restore tmux sessions |
tmux-continuum | Auto-save every 15 min, auto-restore on tmux start |
tmux-fzf | prefix + F opens an fzf picker for sessions/windows/panes/keybindings |
dracula/tmux | Status-bar theme |
Optional plugins commented out in the config (uncomment + prefix + I):
tmux-logging, tmux-sessionist, tmux-menus, tmux-sidebar.
Fresh-machine setup
# tmux must be installed (Brewfile handles macOS; apt install tmux on Debian)
stow tmux # from the dotfiles root
tmux # TPM auto-clones and installs plugins on first launch
vim
Stow package for ~/.vimrc and ~/.vim/. Vim 8+ with vim-plug managing
the plugin set.
Table of contents
- Plugins
- Leader-key cheat sheet
- Other mappings
- Non-default settings
- Window/pane navigation across vim and tmux
- Ripgrep integration
- Go workflow
- Runtime directories
- Fresh-machine setup
Leader is , (let mapleader=',').
Plugins
Managed by vim-plug. Install/update
with :PlugInstall / :PlugUpdate. Clean orphans with :PlugClean!.
Editing essentials (tpope stack)
| Plugin | Use |
|---|---|
vim-commentary | gcc toggles a line, gc{motion} toggles a range |
vim-surround | cs'" → change 'foo' to "foo"; ys{motion}{char}; ds{char} |
vim-repeat | makes the above two .-repeatable |
vim-sleuth | auto-detect indent settings from the file |
vim-fugitive | :Git, :Gdiffsplit, :Gblame, :Git log |
vim-dispatch | async build/test runner |
vim-projectionist | project-aware :A-style file switching |
vim-unimpaired | bracket-pair mappings: ]q/[q quickfix, ]b/[b buffers, ]c/[c (shared with gitgutter), ]<Space> add blank line, etc. |
vim-eunuch | shell-command Ex wrappers: :Rename, :Move, :Delete, :SudoWrite, :Chmod |
UI
| Plugin | Use |
|---|---|
vim-airline | statusline with mode/branch/lint |
bufexplorer | <leader>be/bt/bs/bv — list/toggle/split buffers |
preservim/nerdtree | <leader>nt toggle, <leader>nf reveal current file |
tagbar | <leader>tt outline of symbols (needs ctags) |
undotree | <leader>u visualize undo branches (paired with undofile) |
Fuzzy finder
| Plugin | Use |
|---|---|
junegunn/fzf + junegunn/fzf.vim | :Files, :Rg, :Buffers, :GFiles?, :History |
Editor integrations
| Plugin | Use |
|---|---|
editorconfig-vim | honor .editorconfig files |
vim-tmux-navigator | <C-h/j/k/l> jumps across vim splits and tmux panes |
vim-gitgutter | gutter diff markers; ]c/[c next/prev hunk |
splitjoin.vim | gS/gJ split/join one-liners ↔ blocks |
auto-pairs | auto-close brackets/quotes |
ultisnips | snippet engine |
vim-snippets | the snippet library ultisnips expands (engine ships none on its own) |
Linting
| Plugin | Use |
|---|---|
dense-analysis/ale | async linting (pylint, shellcheck, yamllint). Go is owned by vim-go. |
Languages
| Plugin | Use |
|---|---|
fatih/vim-go | Go IDE-ish features. See Go workflow. |
rust-lang/rust.vim | Rust filetype + rustfmt |
Colors
dracula is the scheme for all filetypes, including Go.
Leader-key cheat sheet
(Leader = ,)
| Keys | Action |
|---|---|
,<space> | clear search highlight |
,, | switch to last file |
,ev / ,sv | edit / source $MYVIMRC |
,ln / ,nw / ,rn | toggle number / wrap / relativenumber |
,sc | toggle spell check |
,tn / ,tc | new / close tab |
,bd / ,bda | close current buffer / close all buffers |
,dc / ,do | close diff / :DiffOrig (diff against unsaved) |
,rt | retab |
,url | open URL under cursor (OS default browser) |
,gg | toggle gitgutter line highlights |
,tt | toggle tagbar |
,nt / ,nf | toggle / reveal NERDTree |
,u | toggle undotree |
,f / <C-p> | fzf :Files |
,b | fzf :Buffers |
,a | fzf :Rg (prompt for pattern) |
,* | fzf :Rg word under cursor |
,h | fzf :History |
,q | close quickfix |
<C-n> / <C-m> | :cnext / :cprevious |
Other mappings
Non-leader bindings worth knowing:
| Keys | Action |
|---|---|
jj (insert) | <Esc> — escape without leaving home row |
<Down> / <Up> (insert) | move by visual line (gj/gk), so wrapped lines work intuitively |
j / k (normal) | move by visual line (gj/gk) |
<M-j> / <M-k> (normal + visual) | move the current line / selection down / up |
< / > (visual) | indent left / right and keep the selection (gv re-select) |
<tab> (normal + visual) | jump to matching bracket (rebinds %) |
n / N / * / # | re-center the screen on the match (Nzz) |
/ | starts a “very magic” search (/\v — most punctuation is regex metachar) |
Q | gq — reformat with motion (the default Q ex mode is rarely useful) |
Custom Ex commands:
| Command | Action |
|---|---|
:W / :SudoWrite | sudo-save the current buffer (:W is an alias for vim-eunuch’s :SudoWrite) |
:DiffOrig | vertical diff between the buffer and the on-disk file (,do triggers it) |
Non-default settings
| Setting | Reason |
|---|---|
hidden | allow buffer switching with unsaved changes |
clipboard=unnamed,unnamedplus | system clipboard on Mac (unnamed) and Linux (unnamedplus) |
termguicolors | true color; pairs with tmux terminal-overrides ",xterm-256color:Tc" |
splitbelow, splitright | new splits open in more intuitive positions |
completeopt=menu,menuone,noselect | dropdown without auto-selecting first match |
mouse=a | mouse on |
undofile, undolevels=500 | persistent undo across sessions |
backup, backupdir=~/.vim/backup | keep backup files in a known place |
directory=~/.vim/tmp, undodir=~/.vim/undo | move swap/undo out of the working dir |
colorcolumn="80," + range(120,999) | line marker at 80, solid color from 120+ |
textwidth=200 | global default; overridden to 80 for FileType text |
ignorecase + smartcase | case-insensitive unless query has uppercase |
tags=tags;/ | walk up looking for ctags tags file |
Trailing whitespace is highlighted in red and stripped on save for
*.go/*.py/*.sh. The motion/search rebindings that pair with
these settings (visual-line j/k, recentering n/*, <tab> for
bracket matching) are listed under Other mappings.
Window/pane navigation across vim and tmux
<C-h/j/k/l> is bound in both. With vim-tmux-navigator and tmux’s
matching keybindings, hitting <C-l> from a rightmost vim split jumps
into the tmux pane to the right. Same in reverse.
Ripgrep integration
:Rg foo— interactive fzf picker over rg results (fromfzf.vim).,*—:Rgfor the word under the cursor.:grep foo— batch search; populates the quickfix list. Uses rg via:set grepprg=rg\ --vimgrep\ --smart-case\ --hidden set grepformat=%f:%l:%c:%m- For “live rg + fzf, open at line in editor” from outside vim, see the
frgfunction infzf/README.md.
Go workflow
vim-go is configured with:
g:go_fmt_command="goimports"— format + organize imports on saveg:go_metalinter_autosave=1— run linter on saveg:go_auto_sameids=1,g:go_auto_type_info=1— highlight same identifiers, show types- Many
g:go_highlight_*=1flags for richer syntax
Go-specific leader maps (active only in .go files):
| Keys | Action |
|---|---|
,b | :GoBuild or :GoTestCompile based on filename |
,c | toggle coverage |
,t | test |
,r | run |
,e | rename (gopls) |
,i | info under cursor |
,s | implements |
,ds/,dt/,dv | def in split/tab/vsplit |
,gd/,gv/,gb | doc / doc-vertical / doc-browser |
,gl | metalinter |
,ga | :GoAlternate (jump between code and test file) |
Runtime directories
The repo includes empty .gitignore files in these dirs to keep them
present in stow targets without committing the runtime data:
| Dir | What goes there |
|---|---|
~/.vim/backup | *~ backup files (gitignored repo-wide) |
~/.vim/tmp | swap files |
~/.vim/undo | persistent undo blobs |
~/.vim/plugged | vim-plug-managed plugins (not in repo) |
Fresh-machine setup
stow vim # symlinks ~/.vimrc and ~/.vim/
vim +"PlugInstall --sync" +qall
For headless (CI/script) install, vim-plug needs a real pty — run inside
a temporary tmux session (see CLAUDE.md at the repo root).
Xresources
Stow package for ~/.Xresources. Linux/X11 only — used by URxvt,
xterm, and other X11 apps that read the resource database.
Table of contents
Palette: Wombat
A dark-background terminal palette derived from the Wombat color scheme.
| Slot | Color |
|---|---|
*background | #161616 (near-black) |
*foreground | #ffffff |
color0..7 | normal palette (black, red, green, yellow, blue, magenta, cyan, white) |
color8..15 | bright palette |
Defined as full *colorN: rgb:RR/GG/BB entries so they apply to any X
client reading the resource database, not just URxvt.
URxvt
Non-default settings:
| Resource | Value | Why |
|---|---|---|
URxvt*font / boldFont | xft:Consolas:size=12 | Consolas-based; needs MS fonts installed |
URxvt*depth | 32 | enable ARGB visuals (real transparency) |
URxvt*transparent | true + shading: 10 | translucent terminal |
URxvt*buffered | true | double-buffered redraw |
URxvt*cursorBlink | true | |
URxvt*saveLines | 65535 | large scrollback |
URxvt*scrollBar | false | |
URxvt*scrollTtyOutput | false | don’t jump on program output |
URxvt*scrollTtyKeypress | true | jump on keypress |
URxvt*scrollWithBuffer | true | scrollback follows new lines while at the bottom |
URxvt*secondaryScreen | true + secondaryScroll: false | alt-screen apps don’t pollute scrollback |
URxvt*loginShell | true | spawn as a login shell |
URxvt*inheritPixmap | true | inherit root pixmap (for true transparency vs. shading) |
xterm
Lighter touch — xterm is mostly used as a fallback.
| Resource | Value |
|---|---|
xterm*faceName | Consolas:style=Regular:size=12 |
xterm*loginShell | true |
xterm*saveLines | 65535 |
xterm*charClass | custom word-class string so double-click selects URLs and paths |
xterm*boldMode | false |
xterm*eightBitInput | false (so Alt doesn’t get encoded) |
Xft (font rendering)
Xft.antialias: true
Xft.autohint: false
Xft.dpi: 92
Xft.hinting: true
Xft.hintstyle: hintslight
Xft.lcdfilter: lcddefault
Xft.rgba: rgb
The DPI is hard-coded to 92 here, but i3/.config/i3/config runs
xrandr --dpi 165 on autostart, which can override. Reconcile per host
if you care.
Activating changes
xrdb -merge ~/.Xresources # apply without restarting X
# or
xrdb ~/.Xresources # replace the database wholesale
Already-running terminals won’t pick up changes until restart.
Fresh-machine setup
apt install rxvt-unicode-256color # for URxvt; xterm ships with most X installs
stow Xresources # symlinks ~/.Xresources
xrdb -merge ~/.Xresources
Not stowed by default by bootstrap-debian.sh — only relevant on
graphical Linux machines.
yamllint
Stow package for ~/.config/yamllint/config. yamllint is a YAML
linter — invoked by lefthook on pre-commit, by ALE in vim, and by
hand.
Table of contents
Layout
| File | Stows to |
|---|---|
yamllint/.config/yamllint/config | ~/.config/yamllint/config |
yamllint only reads this file when run without -c <path> and when
the project has no .yamllint/.yamllint.yml/.yamllint.yaml of its
own.
Rule tweaks
Starting from extends: default:
| Rule | Change | Why |
|---|---|---|
line-length: 120 (warning) | bump from 80, demote to warning | nobody wraps YAML at 80 |
document-start | disable | --- is rarely useful in single-doc files |
truthy.check-keys: false | don’t flag on: keys | GitHub Actions uses on: as a top-level key |
comments.min-spaces-from-content: 1 | down from 2 | the default is overly strict |
comments-indentation | disable | mis-indented comments are common in real YAML and rarely matter |
indentation.indent-sequences: consistent | don’t require nested under key | matches both common styles |
braces / brackets max-spaces-inside: 1 | allow { k: v } | flow style is fine |
Per-project overrides
Drop a .yamllint in your repo root — it wins. Lefthook’s pre-commit
hook (template at lefthook/lefthook.yml) will use whichever yamllint
finds first.
Fresh-machine setup
brew install yamllint # in the Brewfile
stow yamllint
zsh
Stow package for ~/.zshrc. Oh My Zsh + powerlevel10k + a curated plugin set.
Table of contents
- Theme: powerlevel10k
- Plugins
- Completion
- PATH
- History
- Shell behavior
- Environment
- Tool integrations
- OMZ behavior
- What’s loaded from elsewhere
Theme: powerlevel10k
ZSH_THEME="powerlevel10k/powerlevel10k". The instant-prompt preamble is at
the top of .zshrc and must stay there. Configured prompt lives in
~/.p10k.zsh (see p10k/ stow package); .zshrc sources it if present.
Plugins
aliases colored-man-pages command-not-found cp dirhistory direnv docker
extract fzf gitfast golang helm kind kube-ps1 kubectl skaffold sudo
taskwarrior tig tmux
Highlights of non-obvious ones:
| Plugin | Why |
|---|---|
gitfast | Replaces git’s built-in completion with the official upstream Git completion — faster on big repos. |
command-not-found | Suggests how to install missing commands (apt install foo / brew install foo). |
extract | x file.{zip,tar.gz,7z,...} — one command for any archive. |
sudo | Esc Esc prepends sudo to the current line (or recalls the previous one with sudo). |
dirhistory | Alt+←/→ walks back/forward through cd history. Pairs with AUTO_PUSHD. |
golang | Adds Go aliases (g, gob, goc, gor, …) and completion. |
kube-ps1 | Current k8s context/namespace in the prompt. |
Custom plugins (conditionally loaded)
Loaded only if the directory exists under ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/.
| Plugin | Effect |
|---|---|
you-should-use | Nags when you type the long form of an alias. |
zsh-autosuggestions | Greyed-out command completion from history; right-arrow to accept. |
fzf-tab | Replaces zsh’s default completion menu with an fzf picker. Must load last (after every plugin that registers completions). |
Install on a fresh machine:
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
git clone --depth=1 https://github.com/Aloxaf/fzf-tab "$ZSH_CUSTOM/plugins/fzf-tab"
git clone --depth=1 https://github.com/MichaelAquilina/zsh-you-should-use "$ZSH_CUSTOM/plugins/you-should-use"
The bootstrap-debian.sh script does this automatically.
Completion
Before OMZ runs compinit, the config prepends Homebrew completion dirs
to FPATH so brew-installed tools (gh, lazygit, yq, stern, etc.)
get proper tab-completion:
FPATH="$(brew --prefix)/share/zsh-completions:$(brew --prefix)/share/zsh/site-functions:$FPATH"
ZSH_DISABLE_COMPFIX=true
ZSH_DISABLE_COMPFIX=true skips OMZ’s complaint about Homebrew’s
group-writable share/ directory (which is intentional on Homebrew, not
fixable without fighting brew).
The zsh-completions brew formula provides extra completion definitions
beyond what individual tools ship.
zstyle polish (after OMZ’s compinit):
zstyle ':completion:*' menu select # arrow-key menu nav
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # case-insensitive
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}" # colored menu
zstyle ':completion:*' rehash true # pick up new $PATH binaries
PATH
export PATH=$PATH:$HOME/bin
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN
export PATH=$HOME/.local/bin:$PATH # pipx / user-installed binaries
export PATH=$PATH:/usr/local/go/bin
GEM_HOME is set only if ruby is on PATH (guarded).
On macOS, Homebrew’s GNU coreutils/findutils/sed/tar/which/grep/binutils get prepended to PATH so GNU semantics win over the BSD defaults:
if [[ "$OSTYPE" == darwin* ]]; then
for gnu_tool in binutils coreutils findutils gnu-sed gnu-tar gnu-which grep; do
gnu_bin="/opt/homebrew/opt/$gnu_tool/libexec/gnubin"
[[ "$gnu_tool" == "binutils" ]] && gnu_bin="/opt/homebrew/opt/$gnu_tool/bin"
[[ -d "$gnu_bin" ]] && export PATH="$gnu_bin:$PATH"
done
fi
History
HISTFILE = ~/.zsh_history
HISTSIZE = 500000 # in-memory
SAVEHIST = 500000 # on disk
Setopts beyond OMZ defaults:
HIST_FCNTL_LOCK,HIST_FIND_NO_DUPS,HIST_IGNORE_ALL_DUPS,HIST_LEX_WORDS,HIST_NO_FUNCTIONS,HIST_NO_STORE,HIST_REDUCE_BLANKS,HIST_SAVE_NO_DUPSHISTORY_IGNORE="(ls|cd|pwd|exit)"— never store these
OMZ’s defaults provide SHARE_HISTORY, EXTENDED_HISTORY,
HIST_EXPIRE_DUPS_FIRST, HIST_IGNORE_SPACE, HIST_VERIFY — re-affirmed
in the config for clarity.
atuin replaces Ctrl+R with a SQLite-backed TUI on
top of this history.
Shell behavior
EXTENDED_GLOB # ^pat (negate), ~pat (exclude), **/* (recursive)
NO_BEEP # silence the bell
INTERACTIVE_COMMENTS # # comments allowed in interactive lines
AUTO_CD # `dirname` alone cd's there
AUTO_PUSHD # every cd pushes the old dir onto the stack
PUSHD_IGNORE_DUPS
PUSHD_SILENT
COMPLETE_IN_WORD # complete from cursor position, not only end
ALWAYS_TO_END # cursor goes to end after completion
Environment
EDITOR = vim
LANG = en_US.UTF-8
LESS = -RFX # raw colors, quit-if-one-screen, no alt-screen
LESSHISTFILE = - # don't write ~/.lesshst
MANPAGER = sh -c 'col -bx | bat -l man -p' # syntax-highlighted man pages
BAT_THEME = ansi # bat respects terminal palette
RIPGREP_CONFIG_PATH = $HOME/.config/ripgrep/config # rg won't read its config without this
Try man ssh after a fresh shell — colorized.
Tool integrations
| Tool | Wiring |
|---|---|
| zoxide | eval "$(zoxide init zsh)" (guarded on command -v) |
| atuin | eval "$(atuin init zsh)" (guarded; replaces Ctrl+R) |
| eza | when installed: ls is a function (falls back to real ls for short flag bundles with t, e.g. ls -ltrh), plus ll/la/lt/ltrh aliases |
| git | curated shell shortcuts: gst ga gaa gc gca gco gcb gd gds gp gpf glg (names match the OMZ git plugin, which isn’t enabled — gitfast gives completion only) |
| kitty | kt/kw rename + kgo <title> jump-by-name + ktabs listing (uses jq) when installed |
| fzf | OMZ fzf plugin auto-sources ~/.fzf.zsh (see fzf/) |
| zsh-syntax-highlighting | sourced at the end; tries Linux, Apple Silicon brew, Intel brew paths in order |
OMZ behavior
zstyle ':omz:update' mode reminder # prompt when updates are available
zstyle ':omz:update' frequency 14 # check every 2 weeks
DISABLE_UNTRACKED_FILES_DIRTY=true # faster git status in big repos
COMPLETION_WAITING_DOTS=true
What’s loaded from elsewhere
~/.fzf.zsh(auto-sourced by OMZfzfplugin) — fzf options + thefrgripgrep+fzf function. Seefzf/README.md.~/.p10k.zsh— prompt configuration. Seep10k/README.md.