My Keyboard-Driven Mac Setup

· 11 min read · Syed Omar Faruk Towaha
My Keyboard-Driven Mac Setup

Many Vim users go through a phase where the config becomes the hobby: a .vimrc that grows to hundreds of lines, copied from blog posts, forum answers and a colleague's dotfiles, held together by comments like " don't touch this, it breaks everything. Every new laptop means a weekend of reinstalling plugins and remembering why half of them are there.

My setup today is the opposite: almost embarrassingly small. I use Neovim, I start from NvChad, and my personal customisations fit on one screen. My editor is faster, prettier and more capable than it has ever been, and I spend my time writing code instead of maintaining a config.

The same idea runs through the whole desk: a tiling window manager driven by Vim keys, a fast terminal, a handful of excellent command-line tools, and an editor that's mostly someone else's (very good) work. Here's the whole thing, from the windows down to the keystrokes.

The whole desk
Windows, terminal, shell tools, editor, and a tiny layer of me.

The windows: AeroSpace, skhd and borders

Before the editor, there's the desktop. macOS window management is, politely, a lot of dragging. I use AeroSpace, a tiling window manager inspired by i3: every window gets tiled automatically, nothing overlaps, and I move around with the keyboard.

The best part is that it speaks Vim. Focus and movement use h j k l, the same keys I use inside the editor, so moving between windows and moving inside a file feel like one skill:

Keys
skhd launches apps; AeroSpace focuses, moves and switches workspaces.

Workspaces are pinned to specific monitors, so things always live in the same place. Workspaces 1 to 5 belong to one screen, 6 and 7 to another, 8 and 9 to the third:

Monitors
Muscle memory works better when apps always live in the same place.

Some apps just shouldn't be tiled. Small utility windows like System Settings, Calculator, Activity Monitor, Passwords, Find My, Music and the VPN client open as floating windows automatically, using AeroSpace's on-window-detected rules. Gaps are tiny (2 px): I want the screen space, not the aesthetics.

skhd handles app launching. It's a simple hotkey daemon: one line per shortcut, and every app I use daily is one chord away. alt-t opens iTerm, alt-b Safari, alt-z VS Code, alt-m Mail, alt-c Calendar, and so on. Combined with AeroSpace, I almost never touch the Dock.

JankyBorders (borders) draws a coloured outline around windows: a red gradient around the focused window and blue around the rest. With several tiled windows on screen, it answers the question "where is my keyboard focus right now?" at a glance. AeroSpace starts it automatically at login.

# ~/.config/aerospace/aerospace.toml (excerpt)
after-startup-command = ['exec-and-forget borders']

[mode.main.binding]
alt-h = 'focus left'
alt-shift-h = 'move left'
alt-1 = 'workspace --auto-back-and-forth 1'
alt-shift-1 = 'move-node-to-workspace 1'

(The menu bar stays tidy thanks to Ice, which hides icons I don't need to see all the time.)

The terminal: iTerm2 and a Nerd Font

I'm on a Mac, and I live in iTerm2. Split panes, a decent search, profiles and hotkeys: it does everything I need and gets out of the way.

The one thing that matters for Neovim is the font. Modern Neovim setups show icons for file types, Git status and diagnostics, and those icons come from a Nerd Font, a programming font patched with thousands of extra glyphs. I use JetBrains Mono Nerd Font. Without a Nerd Font, you get little boxes and question marks where the icons should be, and you'll think your setup is broken.

brew install --cask font-jetbrains-mono-nerd-font
# then: iTerm2 → Settings → Profiles → Text → Font

The shell: zsh, starship and a few great tools

My shell is zsh with oh-my-zsh, kept to a sensible set of plugins: zsh-autosuggestions (grey suggestions from history as I type), zsh-syntax-highlighting (commands turn red when they're wrong, before I press Enter), z for jumping to frequent directories, sudo (press Esc twice to prefix the last command with sudo), and git.

The prompt is starship: fast, written in Rust, and it shows only what's relevant: the directory, Git branch and status, and the language or environment I'm in.

Then come the command-line tools that quietly replaced the classics:

Tool Replaces Why I like it
lsd ls icons, colours, directories first; lsd --tree as tree
bat cat syntax highlighting and line numbers
ripgrep (rg) grep -r extremely fast, respects .gitignore
fd find simpler syntax, also fast
fzf scrolling and guessing fuzzy finder for files, history and directories
lazygit typing git commands a full Git UI in the terminal
git-delta plain diffs readable, syntax-highlighted diffs
tldr man (sometimes) practical examples instead of manuals
gh the GitHub website PRs, issues and repos from the terminal

fzf deserves a special mention. With its zsh integration, Ctrl-R becomes a fuzzy search over my entire command history, and Ctrl-T fuzzy-finds files. I also use fzf-git, which adds fuzzy pickers for branches, commits and files in Git repos, and I've set fzf to preview directories as an lsd tree when completing cd. It's one of those tools you stop noticing because it's simply how the terminal works now.

A few aliases that save me hundreds of keystrokes a day:

alias v='nvim'
alias cat='bat'
alias ls='lsd'
alias ll='lsd -alF --group-directories-first'
alias tree='lsd --tree'
alias gis='git status'
alias gic='gitmoji -c'          # commit with an emoji, because life is short
alias gu='open "$(git config --get remote.origin.url)"'   # open the repo in the browser
alias w='curl wttr.in/StJohns'  # Newfoundland weather check, essential

tmux: sessions that survive

Inside iTerm2, I run tmux with the catppuccin (mocha) theme. Its status bar shows the session, directory, CPU and battery. Two plugins make it indispensable: tmux-resurrect saves my panes and windows, and tmux-continuum saves and restores them automatically. I can reboot the machine and my sessions, windows and panes come back exactly as they were. Mouse support is on, because I'm a terminal person, not a purist.

The editor: Neovim

Why Neovim instead of classic Vim? Three reasons:

Plain Vim still matters, though: it's on practically every server. SSH into a machine, type vim, and the muscle memory works the same. That's the real superpower of learning Vim: the keystrokes travel with you, even when your config doesn't.

The framework: NvChad

Instead of building a config from scratch, I start from NvChad, a Neovim configuration framework that's fast, good-looking and well maintained. Installing it is one command:

git clone https://github.com/NvChad/starter ~/.config/nvim && nvim

Open Neovim, wait a moment while plugins install, and you get:

The beauty of the starter template is that NvChad itself stays a plugin. Updates don't overwrite my changes, because my changes live in my own small files.

My customisations (all of them)

Leader key: Space. The leader is the prefix for your own shortcuts. Space is the biggest key on the keyboard and your thumbs are always on it.

vim.g.mapleader = " "

Theme: onedark. Easy on the eyes for long sessions, and it matches the dark terminal I use everywhere else. NvChad has dozens of themes; <leader>th opens a picker with live preview.

M.base46 = {
  theme = "onedark",
}

Two mappings that changed my life.

My mappings
The entirety of my personal keymap.

That's it. Two lines. It's tempting to add dozens of custom mappings, but these two are the ones that earn their place every single day.

Language servers. I enable the HTML and CSS servers, since I tinker with websites (like this one) a lot. Adding more is a one-liner, and Mason installs the servers for me:

local servers = { "html", "cssls" }
vim.lsp.enable(servers)

Type :Mason, find the server you need (for Python, pyright), press i to install it, then add its LSP name to the list. Done.

Formatting with conform.nvim. Lua files get formatted with stylua. I trigger formatting manually with <leader>fm rather than on every save, because sometimes I'm editing someone else's file and don't want to reformat the whole thing in my pull request.

The keys I use all day

These are mostly NvChad defaults, with Space as the leader:

Keys What it does
<leader>ff find files (Telescope)
<leader>fw live grep: search text across the project
<leader>fb switch between open buffers
<C-n> toggle the file tree
<leader>/ comment / uncomment a line
<leader>fm format the file
gd go to definition
K show documentation for the thing under the cursor
<leader>th change theme
; / jk my two mappings: command mode / escape

And the Vim fundamentals that make everything else fast: ciw (change inner word), dap (delete a paragraph), . (repeat the last change), * (search for the word under the cursor), % (jump between matching brackets), and macros with q. No plugin beats knowing these.

How it all fits together

For example: alt-t brings up iTerm on workspace 1, tmux restores the project session, z drops me into the repo, v . opens Neovim, <leader>ff finds the file, and alt-l jumps to the browser on the right to check the docs. My hands never leave the home row, and the same h j k l instinct works at every level: windows, panes and text.

Why this approach works for me

If you want to start

  1. Learn Vim motions first. Run vimtutor in your terminal; it takes about 30 minutes and teaches the essentials.
  2. Install a Nerd Font and set it in your terminal.
  3. Install Neovim (brew install neovim on macOS) and clone the NvChad starter.
  4. Use it for real work for two weeks before customising anything. Then add only what you genuinely miss.
  5. Want the rest of the desk? brew install --cask nikitabobko/tap/aerospace, brew install asmvik/formulae/skhd felixkratz/formulae/borders, then brew install fzf bat lsd ripgrep fd lazygit git-delta starship tmux, and add tools one at a time, as you feel the need.

My old config was impressive. My current config is useful. It took me far too long to realise those aren't the same thing.

// related

// prefer the terminal?

Open the terminal blog and type read my-setup.