Editor Setup: clangd + compile_commands.json¶
Your editor is where you spend 8 hours a day. If it doesn’t give you go-to-definition, real-time errors, and rename-symbol for C code, you are paying a compounding tax on every edit. This file gets you a modern C editing environment on Neovim or VS Code in under an hour, powered by clangd — the LSP that has clearly won the C/C++ tooling race by 2026.
The core insight: a good C editor is really a good LSP client pointed at a good LSP server (clangd), which reads a compile_commands.json file describing how each .c in your project is compiled. Get those three pieces right and every editor is roughly equivalent.
Why clangd, and Why Not ccls Anymore¶
Both clangd and ccls are language servers built on Clang’s tooling libraries. Two years ago there was a legitimate debate: ccls had faster project-wide indexing on large codebases, clangd had smoother incremental updates. That gap has closed. As of 2026 the community consensus (r/emacs, r/vim, r/C_Programming, and the MaskRay/ccls#880 thread which the ccls maintainer himself has commented on) is:
clangd is the default. More stable, faster, actively developed by the LLVM project, and ships with every modern Clang install.
ccls has been effectively unmaintained for stretches; contributors have thinned out.
Every mainstream editor’s C plugin now assumes clangd as the reference LSP.
Install:
# macOS (comes with Xcode CLT; brew for a newer version)
brew install llvm # then export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
# Debian/Ubuntu
sudo apt install clangd-18 # or whichever is current
# Verify
clangd --version
compile_commands.json — The One File That Makes Everything Work¶
clangd needs to know: for each .c file, exactly what flags does the compiler use? Include paths, macro definitions, standard version, warnings. That’s what compile_commands.json encodes — one JSON entry per translation unit. Without it, clangd guesses and gets it wrong: false errors, missing includes, #ifdef branches highlighted wrong. With it, everything just works.
Three ways to generate it:
1. CMake (best when you already use CMake)¶
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -s build/compile_commands.json . # so clangd finds it at project root
That’s it. CMake writes the file as a side effect of configure.
2. Bear (best when you use plain Make)¶
bear (Build EAR) intercepts the compiler invocations during a build and writes the JSON. This is what you’ll use in Phase 0 since you’re on plain make.
# macOS
brew install bear
# Ubuntu
sudo apt install bear
# Use it (from a clean state so all files get seen)
make clean
bear -- make
# now compile_commands.json exists in your project root
bear supports incremental appends with bear --append -- make foo.o if you need to add a single new file without a full clean rebuild.
3. compiledb (fallback, pure Python)¶
pip install compiledb
compiledb make
Slightly less reliable than bear (it parses make -n output rather than intercepting execve), but it works when you can’t install a C tool.
Once compile_commands.json exists at your project root, any editor with a clangd plugin picks it up automatically. There is no editor-specific configuration for this — that’s the point of LSP.
Confirm it works from the command line:
clangd --check=src/main.c # should report "0 errors, 0 warnings" (or real issues)
Neovim Setup (~15 min)¶
Neovim 0.10+ ships with a builtin LSP client. You just need to tell it about clangd. Two paths:
Fast path — using nvim-lspconfig:
-- ~/.config/nvim/init.lua (fragment)
require('lspconfig').clangd.setup({
cmd = { 'clangd', '--background-index', '--clang-tidy', '--header-insertion=iwyu' },
on_attach = function(_, buf)
local map = function(k, f) vim.keymap.set('n', k, f, { buffer = buf }) end
map('gd', vim.lsp.buf.definition)
map('gr', vim.lsp.buf.references)
map('K', vim.lsp.buf.hover)
map('<leader>rn', vim.lsp.buf.rename)
map('<leader>ca', vim.lsp.buf.code_action)
end,
})
Zero-config path — using mini.nvim or LazyVim: LazyVim’s C/C++ extra just works; enable it and clangd shows up.
Add nvim-cmp or blink.cmp for autocomplete; trouble.nvim for a nice diagnostics panel. None of that is C-specific.
VS Code Setup (~5 min)¶
Install these extensions:
clangd by LLVM (
llvm-vs-code-extensions.vscode-clangd) — use this, not the Microsoft C/C++ extension. The MS extension has its own IntelliSense engine that fights clangd. Pick one; clangd is the recommended answer for pure C work.CodeLLDB (
vadimcn.vscode-lldb) — better debugger UI than the built-in for lldb on macOS.
VS Code will prompt to disable IntelliSense when clangd is enabled. Say yes.
Optional settings.json tweaks:
{
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=iwyu",
"--completion-style=detailed"
],
"clangd.checkUpdates": true,
"editor.formatOnSave": true
}
.clangd Project Config (Optional but Useful)¶
Drop a .clangd file at your project root to override or add flags without touching compile_commands.json:
CompileFlags:
Add: [-Wall, -Wextra, -Wpedantic, -std=c17]
Remove: [-W*, -Werror] # so warnings show as warnings in editor, not errors
Diagnostics:
UnusedIncludes: Strict
ClangTidy:
Add: [modernize-*, bugprone-*, performance-*]
Remove: [modernize-use-trailing-return-type]
This is a Phase 2 polish. For Phase 0 the auto-detected flags from compile_commands.json are enough.
Formatting: clang-format¶
Drop a .clang-format at project root; every editor with clangd wired up will format-on-save. A sane starting point:
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
PointerAlignment: Right
AlwaysBreakAfterReturnType: None
Personal style is personal. LLVM, Google, and WebKit are the three built-in styles worth trying. Pick one, commit the .clang-format, move on.
What Most People Get Wrong About Editor Setup¶
They spend two weeks tuning their editor before they’ve written any code. Do not do this. The goal of this file is: install clangd, generate compile_commands.json, get go-to-definition working, stop. Everything else — themes, statusline animations, LSP UI plugins, key remaps — is polish. Polish after you’ve shipped Phase 0’s projects, not before.
The second mistake is running the Microsoft C/C++ extension and clangd simultaneously in VS Code. They race, they contradict each other, and their diagnostics disagree. Pick one. On this roadmap, pick clangd.
Quick Verification¶
cd your-project
bear -- make # or use CMake with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ls compile_commands.json
clangd --check=src/main.c
# Open src/main.c in your editor
# - hover a function name → see signature
# - "go to definition" on a function → jumps there
# - introduce a typo → real-time red squiggle
# All three working → you're done.
Return to README.md · Next: projects.md