02 — Editor & Debugger¶
Prereq: 01_toolchain_setup.md complete. clang++ points to brew LLVM.
Time budget: One focused evening (~2–3 hours) to get VSCode fully green, plus a debugger dry run.
Your editor is where you’ll live for twelve months. Configure it once, correctly, on the tools that C++ people actually use in 2026 — not the defaults VSCode nudges you toward.
The choice: VSCode + clangd, or CLion¶
Both are valid. Pick one and commit for at least Phase 0. Switching editors mid-phase burns a weekend you don’t have.
Aspect |
VSCode + clangd + CodeLLDB |
CLion |
|---|---|---|
Cost |
Free |
Free for non-commercial use since May 2025; requires JetBrains account. |
Setup effort |
~30 minutes of config |
~5 minutes; opinionated defaults just work |
Indexing quality |
Excellent (uses real clang frontend) |
Excellent (also uses clangd under the hood since 2023) |
Debugger UX |
CodeLLDB — solid, no frills |
Best-in-class visual debugger |
Memory footprint |
~500 MB with C++ project loaded |
~1.5–2 GB |
CMake integration |
Manual |
Built-in and deeply integrated |
Refactoring |
Basic (rename, extract var/func) |
Comprehensive |
Terminal integration |
Native VSCode terminal |
Built-in |
Recommendation for Raghul |
Default — lighter, closer to CLI reality |
Fine alternative if you already use JetBrains at work |
Verdict: Start with VSCode + clangd + CodeLLDB. It keeps you close to the command line, which is where study partners and production debug sessions live. Revisit CLion in Phase 2 if you want it. This file walks you through the VSCode path in detail and gives a quick CLion setup at the end.
What most people get wrong¶
Two failure modes dominate here. First: installing Microsoft’s “C/C++” extension (
ms-vscode.cpptools) ANDclangdand letting them fight for the same file. Symptoms: red squiggles on valid code, duplicated hover popups, autocompletes that disagree. Pick clangd. Disable cpptools’ IntelliSense (keep only its debugger if you use it — but you’re using CodeLLDB, so uninstall cpptools entirely).Second, and worse: skipping the debugger and relying on
std::coutprintf-debugging like it’s 2005. You will fight this instinct all month. When code misbehaves, your reflex must become: set a breakpoint, run, inspect. Not: add a print, recompile, guess.
VSCode: extensions¶
Install exactly these four. No more.
Extension ID |
Purpose |
Notes |
|---|---|---|
|
Language server: completion, diagnostics, go-to-def, hover, format. |
Official LLVM extension. |
|
Debugger front-end that speaks to |
Do NOT use ms-vscode.cpptools’ debugger. |
|
Syntax highlighting for |
Just highlighting, not the ms-vscode CMake Tools. |
|
CMake build target picker, one-click build/run. |
Nice-to-have. Skip if you prefer terminal. |
Explicitly do NOT install:
ms-vscode.cpptools— conflicts with clangd on the same files; its own IntelliSense is inferior to real clang.Any “C++ IntelliSense” third-party extension.
Install from the terminal so you get the exact IDs, not a lookalike:
code --install-extension llvm-vs-code-extensions.vscode-clangd
code --install-extension vadimcn.vscode-lldb
code --install-extension twxs.cmake
# optional:
code --install-extension ms-vscode.cmake-tools
VSCode: user settings.json¶
Open the command palette (Cmd+Shift+P), run Preferences: Open User Settings (JSON), and merge in:
{
"clangd.path": "/opt/homebrew/opt/llvm/bin/clangd",
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--header-insertion=iwyu",
"--pch-storage=memory",
"--all-scopes-completion",
"--suggest-missing-includes",
"-j=4"
],
"clangd.onConfigChanged": "restart",
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
"editor.formatOnSave": true,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.rulers": [100]
},
"editor.inlayHints.enabled": "onUnlessPressed",
"files.associations": {
"*.tpp": "cpp",
"*.ipp": "cpp"
}
}
Rationale for each clangd argument:
--background-index— build a project-wide symbol index in the background. Enables fast global “go to symbol.”--clang-tidy— runclang-tidydiagnostics inline. Free code review.--completion-style=detailed— richer completion items (parameter names, return types).--header-insertion=iwyu— add#includes only when clangd thinks the file directly needs them. Cuts include bloat.--pch-storage=memory— keep precompiled headers in RAM instead of disk. Faster on Apple Silicon.--all-scopes-completion— offer completions from all namespaces, not just currently-included ones.--suggest-missing-includes— clangd will point you at the header you forgot to include.-j=4— four background threads. Adjust to your CPU core count; 4 is fine on M1/M2/M3.
Project-level .clangd config¶
Drop a .clangd file at the root of any project. It tells clangd how to interpret files it can’t find in compile_commands.json (e.g., new files you just added).
# .clangd
CompileFlags:
Add:
- -std=c++20
- -Wall
- -Wextra
- -Wpedantic
Compiler: /opt/homebrew/opt/llvm/bin/clang++
Diagnostics:
ClangTidy:
Add:
- modernize-*
- performance-*
- readability-*
- bugprone-*
Remove:
- modernize-use-trailing-return-type
- readability-magic-numbers
- readability-identifier-length
UnusedIncludes: Strict
MissingIncludes: Strict
InlayHints:
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
Commit this file alongside your source. Every project in Phase 0 onward gets one.
compile_commands.json — the file that makes clangd real¶
clangd is only as smart as the flags it thinks you’re using. It reads compile_commands.json, a JSON array where each entry says “for this source file, use these exact compiler flags.” Without it, clangd falls back to guessing and you get spurious errors on perfectly valid code.
Generate it two ways:
With CMake (preferred):
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # in CMakeLists.txt
Then configure:
cmake -B build -G Ninja
ln -sf build/compile_commands.json . # symlink to project root so clangd finds it
The symlink is important. clangd searches upward from each source file for compile_commands.json, and it looks in the project root first, not in build/.
Without CMake (for single-file drills): use bear:
brew install bear
bear -- make # or: bear -- clang++ ...
bear intercepts your build commands and writes compile_commands.json in the current directory.
launch.json for CodeLLDB¶
In your project, create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug (current target)",
"program": "${workspaceFolder}/build/${input:targetName}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cmake-build",
"stopOnEntry": false
},
{
"type": "lldb",
"request": "launch",
"name": "Debug tests",
"program": "${workspaceFolder}/build/tests",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cmake-build"
}
],
"inputs": [
{
"id": "targetName",
"type": "promptString",
"description": "Target binary name (e.g., word_counter)",
"default": "main"
}
]
}
And .vscode/tasks.json for the pre-launch build:
{
"version": "2.0.0",
"tasks": [
{
"label": "cmake-build",
"type": "shell",
"command": "cmake --build build",
"group": { "kind": "build", "isDefault": true },
"problemMatcher": ["$gcc"]
},
{
"label": "cmake-configure",
"type": "shell",
"command": "cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug"
}
]
}
The debugger workflow — the seven moves¶
These are the only interactions you need for 95% of debug sessions. Practice each one on a broken drill.
Set a breakpoint. Click the gutter next to a line number. Red dot appears. In terminal
lldb:b file.cpp:42.Run.
F5in VSCode. Inlldb:run(orr).Step over.
F10. Executes the current line without descending into function calls.lldb:n(next).Step into.
F11. Descends into the function call on the current line.lldb:s(step).Step out.
Shift+F11. Runs to the end of the current function and stops in the caller.lldb:finish.Inspect a variable. Hover, or open the Variables pane. In
lldb:p varname. To print astd::vector’s contents:p vec. To print through a pointer:p *ptr.Print backtrace. Debug Console:
bt. Shows the call stack up to the current frame. Essential when you hit a crash.
Reading pointers in lldb — the trick returning devs forget¶
(lldb) p ptr # prints the address
(lldb) p *ptr # prints the value at that address
(lldb) memory read --size 4 --format d --count 8 ptr
# 8 ints of 4 bytes each in decimal, starting at ptr
(lldb) p (int[8])*ptr # cast + dereference — shows an array of 8 ints
For a std::string, std::vector, std::map, std::unique_ptr: LLDB has data formatters built in. Just p obj. If output is unreadable, run type summary list to see what formatters are loaded.
Verifying the whole stack — the 5-minute smoke test¶
Do this before you close this document. If any step fails, fix it now.
mkdir -p ~/cpp-scratch/smoke && cd ~/cpp-scratch/smoke
cat > main.cpp <<'EOF'
#include <print>
#include <vector>
#include <string>
int main() {
std::vector<std::string> names = {"alice", "bob", "carol"};
for (auto& n : names) {
std::println("hi, {}", n);
}
return 0;
}
EOF
cat > CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.28)
project(smoke CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
add_executable(smoke main.cpp)
EOF
cmake -B build -G Ninja
cmake --build build
ln -sf build/compile_commands.json .
code .
Inside VSCode:
Open
main.cpp.Hover over
std::vector— you should see the definition popup within 2–3 seconds. If not, clangd isn’t findingcompile_commands.json.Type
std::prinsidemain— completions should includeprintln,print. If completions don’t appear, clangd path is wrong.Set a breakpoint on the
forline.F5, select the LLDB launch config, verify you stop at the breakpoint.F10to step over each iteration. Watchnchange in the Variables pane.Type
btin the debug console. You should seemain.
If all seven steps pass, you are done with editor setup for the year.
CLion — the quick alternative¶
If you’d rather use CLion (JetBrains, free for non-commercial use since May 2025):
Install:
brew install --cask clionor download from jetbrains.com/clion.Sign in with a JetBrains account and pick the free non-commercial license during first launch.
Settings → Build, Execution, Deployment → Toolchains: add a toolchain pointing to
/opt/homebrew/opt/llvm/bin/clangand/opt/homebrew/opt/llvm/bin/clang++.Settings → CMake: add
-DCMAKE_EXPORT_COMPILE_COMMANDS=ONto CMake options.Open your project folder. CLion configures, indexes, and gives you a working debugger in one shot.
CLion’s debugger is a genuine upgrade over CodeLLDB — the memory view, watches, and evaluate-expression panels are excellent. Its cost is RAM (~1.5–2 GB) and a small annual account renewal step for the free tier. For Raghul’s laptop and workflow, VSCode is lighter and closer to what you’ll ship in a container later; recommend it as default and revisit CLion if you find yourself hitting VSCode’s debugger limits.
Exit checklist for this file¶
clangd extension installed; MS cpptools extension not installed.
~/Library/Application Support/Code/User/settings.jsonhas the clangd config from above.Smoke-test project builds and hovers/completions work in VSCode.
.clangdfile understood; you can name three checks it enables.You can name the seven debugger moves and their keyboard shortcuts.
You can print a pointer’s target and a
std::vector’s contents insidelldb.
Return to Phase 0 README · Next: 03_syntax_refresh_drills.md