02 — VSCode Production C++20/23 Config¶
Goal. In ~30 minutes, land a VSCode configuration you don’t touch again for 13 months. Every file below is copy-pasteable. Every setting is justified inline.
This file assumes you’ve already completed
01_day1_macos_setup.md — you have brew LLVM
on PATH, CMake, Ninja, and the VSCode extension bundle.
§1. The five dotfiles you commit to every C++ project¶
Every serious C++ repo you create for the next 13 months should have these
five files at the root (or in .vscode/ where relevant):
File |
Purpose |
Lives at |
|---|---|---|
|
Configures the clangd language server |
Repo root |
|
Formatting rules — LLVM base |
Repo root |
|
Static analysis rule set |
Repo root |
|
Per-workspace VSCode settings |
|
|
Build / test / benchmark tasks |
|
|
LLDB debug launch configs |
|
Also in the repo: .editorconfig (universal editor sanity) and
.gitignore (ignore build/, .cache/, etc).
Commit all of these on first commit of every new project. Do not add-them-later; the LLM-style “add the config at the end” habit is exactly what causes clangd to misconfigure and infect your understanding for a week.
§2. User-level VSCode settings.json¶
Open VSCode → Cmd+Shift+P → “Preferences: Open User Settings (JSON)”.
Replace the file contents with:
{
// === Editor essentials ===
"editor.fontSize": 14,
"editor.fontFamily": "JetBrainsMono Nerd Font, Menlo, monospace",
"editor.fontLigatures": true,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.rulers": [80, 100],
"editor.renderWhitespace": "boundary",
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.wordWrap": "off",
"editor.minimap.enabled": false,
"editor.stickyScroll.enabled": true,
"editor.inlayHints.enabled": "onUnlessPressed",
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.suggest.showKeywords": true,
"editor.suggestSelection": "first",
"editor.acceptSuggestionOnEnter": "off",
// === Files ===
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.exclude": {
"**/.cache": true,
"**/build": true,
"**/.DS_Store": true,
"**/CMakeFiles": true,
"**/compile_commands.json": false
},
// === clangd is the ONLY C/C++ language server ===
// (We deliberately do NOT install ms-vscode.cpptools.)
"clangd.path": "/opt/homebrew/opt/llvm/bin/clangd",
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--function-arg-placeholders=false",
"--header-insertion=iwyu",
"--pch-storage=memory",
"--all-scopes-completion",
"--suggest-missing-includes",
"-j=8"
],
"clangd.checkUpdates": false,
"clangd.onConfigChanged": "restart",
// === CMake Tools ===
"cmake.configureOnOpen": true,
"cmake.buildDirectory": "${workspaceFolder}/build",
"cmake.generator": "Ninja",
"cmake.exportCompileCommandsFile": true,
"cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json",
"cmake.parallelJobs": 8,
"cmake.buildBeforeRun": true,
// === LLDB debugging (CodeLLDB extension) ===
"lldb.showDisassembly": "auto",
"lldb.dereferencePointers": true,
"lldb.consoleMode": "commands",
// === Error Lens — inline diagnostics ===
"errorLens.enabledDiagnosticLevels": ["error", "warning", "info"],
"errorLens.excludeBySource": [],
"errorLens.followCursor": "allLines",
// === Git / GitLens ===
"git.confirmSync": false,
"git.autofetch": true,
"git.enableSmartCommit": true,
"gitlens.hovers.currentLine.over": "line",
// === Copilot policy — OFF for Phases 0–2 ===
"github.copilot.enable": {
"*": false,
"cpp": false,
"markdown": false
},
"github.copilot.chat.enable": false,
// === Telemetry ===
"telemetry.telemetryLevel": "off",
"redhat.telemetry.enabled": false,
// === Workbench ===
"workbench.editor.enablePreview": false,
"workbench.colorTheme": "Default Dark Modern",
"workbench.iconTheme": "vs-seti",
// === Terminal (macOS zsh) ===
"terminal.integrated.defaultProfile.osx": "zsh",
"terminal.integrated.fontSize": 13,
"terminal.integrated.scrollback": 100000,
// === Language-specific ===
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
"editor.tabSize": 4
},
"[c]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"[cmake]": {
"editor.tabSize": 2
},
"[python]": {
"editor.tabSize": 4
},
"[json]": {
"editor.tabSize": 2
}
}
Key policy calls in this settings block:
Copilot disabled — aligned with M1 no-AI-for-fundamentals rule in
00_command/README.md. Turn on selectively from Phase 3 onward, editing thegithub.copilot.enablemap.clangdis the only C++ intelligence provider. We explicitly forbidms-vscode.cpptoolsin01_day1_macos_setup.md§6.editor.acceptSuggestionOnEnter: off— prevents you from accepting the wrong suggestion mid-newline. Learners do this constantly.editor.rulers: [80, 100]— nudges you toward Google-ish line width. Not enforced by.clang-format, just visual.clangd.arguments— background index, tidy on, IWYU header insertion, 8 parallel workers. Tuned for M1/M2/M3 with 8 perf cores.
§3. .clangd (per-project, at repo root)¶
# .clangd — project-level configuration for the clangd language server
CompileFlags:
Compiler: /opt/homebrew/opt/llvm/bin/clang++
Add:
- -std=c++23
- -stdlib=libc++
- -Wall
- -Wextra
- -Wpedantic
- -Wshadow
- -Wnon-virtual-dtor
- -Wold-style-cast
- -Wcast-align
- -Woverloaded-virtual
- -Wconversion
- -Wsign-conversion
- -Wnull-dereference
- -Wdouble-promotion
- -Wformat=2
Remove:
- -W* # let clangd's own warning set win over CMake
Diagnostics:
ClangTidy:
Add:
- modernize-*
- performance-*
- readability-*
- bugprone-*
- cppcoreguidelines-*
- portability-*
Remove:
- modernize-use-trailing-return-type
- readability-magic-numbers
- readability-identifier-length
- cppcoreguidelines-avoid-magic-numbers
- cppcoreguidelines-pro-bounds-array-to-pointer-decay
UnusedIncludes: Strict
MissingIncludes: Strict
InlayHints:
Designators: Yes
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
Hover:
ShowAKA: Yes
Completion:
AllScopes: Yes
Why the Remove list under ClangTidy:
modernize-use-trailing-return-type— stylistically noisy. Turn on only if you personally likeauto foo() -> int.readability-magic-numbersandcppcoreguidelines-avoid-magic-numbers— too noisy for numerical / algorithms code (Phase 2, Phase 5).readability-identifier-length— fights everyi,j,x,y.
All five are toggleable. Turn back on when doing a Phase 4 codebase audit.
§4. .clang-format (per-project)¶
LLVM base with a few adjustments toward Google-ish for study-visible code:
---
BasedOnStyle: LLVM
Language: Cpp
Standard: c++20
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IncludeBlocks: Regroup
IndentCaseLabels: false
IndentPPDirectives: BeforeHash
NamespaceIndentation: None
PointerAlignment: Left
ReferenceAlignment: Left
ReflowComments: true
SeparateDefinitionBlocks: Always
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
IncludeCategories:
- Regex: '^<[^\.]*>$' # C++ standard headers (no dot)
Priority: 1
- Regex: '^<.*\.h(pp)?>$' # C-style / third-party headers
Priority: 2
- Regex: '^".*"$' # local headers
Priority: 3
Run once against your template:
clang-format -i src/main.cpp
Inspect the diff. If the changes surprise you, your muscle-memory formatting is not aligned to this file — fine, you’ll adapt in 2 days.
§5. .clang-tidy (per-project)¶
---
Checks: >
-*,
bugprone-*,
performance-*,
modernize-*,
readability-*,
cppcoreguidelines-*,
portability-*,
clang-analyzer-*,
-modernize-use-trailing-return-type,
-readability-magic-numbers,
-readability-identifier-length,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-bugprone-easily-swappable-parameters
WarningsAsErrors: 'bugprone-*,performance-*,clang-analyzer-*'
HeaderFilterRegex: '^(?!.*/(build|third_party|external)/).*'
FormatStyle: file
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.MemberCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberSuffix
value: _
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ConstexprVariableCase
value: CamelCase
- key: readability-identifier-naming.ConstexprVariablePrefix
value: k
- key: readability-function-cognitive-complexity.Threshold
value: 25
Run ad hoc from the terminal:
clang-tidy src/main.cpp -- -std=c++23 -stdlib=libc++
Or let clangd surface it inline in the editor (clangd.arguments in §2
includes --clang-tidy).
§6. .vscode/tasks.json¶
Minimal tasks: configure, build, test, clean, run benchmark.
{
"version": "2.0.0",
"tasks": [
{
"label": "CMake: Configure (Debug)",
"type": "shell",
"command": "cmake",
"args": [
"-B", "build",
"-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Debug",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
],
"problemMatcher": ["$gcc"],
"group": "build"
},
{
"label": "CMake: Configure (Release)",
"type": "shell",
"command": "cmake",
"args": [
"-B", "build",
"-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
],
"problemMatcher": ["$gcc"]
},
{
"label": "CMake: Build",
"type": "shell",
"command": "cmake",
"args": ["--build", "build", "--parallel"],
"group": { "kind": "build", "isDefault": true },
"problemMatcher": ["$gcc"]
},
{
"label": "CTest: Run tests",
"type": "shell",
"command": "ctest",
"args": ["--test-dir", "build", "--output-on-failure", "--parallel", "8"],
"group": { "kind": "test", "isDefault": true }
},
{
"label": "Clean build/",
"type": "shell",
"command": "rm",
"args": ["-rf", "build"]
}
]
}
Bind Cmd+Shift+B to the default build task — already the VSCode default.
§7. .vscode/launch.json (LLDB via CodeLLDB)¶
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug (LLDB) — main binary",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/build/hello",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "CMake: Build",
"stopOnEntry": false,
"env": {},
"terminal": "integrated"
},
{
"name": "Debug (LLDB) — pick executable",
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "CMake: Build"
}
]
}
Set a breakpoint in src/main.cpp, hit F5, the CodeLLDB debugger opens.
If it errors, verify §6 preLaunchTask runs first.
§8. .editorconfig and .gitignore¶
# .editorconfig — universal editor sanity
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{md,yml,yaml,json}]
indent_size = 2
[Makefile]
indent_style = tab
# .gitignore — C++ / CMake / macOS baseline
build/
cmake-build-*/
.cache/
.vscode/*.log
compile_commands.json
# macOS
.DS_Store
._*
# CLion / JetBrains
.idea/
*.iml
# Python (uv, venv) — Phase 5
.venv/
__pycache__/
*.pyc
# Conan
CMakeUserPresets.json
conan-cache/
# Coverage / profiling
*.gcda
*.gcno
*.gcov
coverage/
callgrind.out.*
perf.data*
§9. Keybindings worth learning immediately¶
Add to keybindings.json (Cmd+K Cmd+S → “Open Keyboard Shortcuts (JSON)”):
[
{ "key": "cmd+shift+b", "command": "workbench.action.tasks.build" },
{ "key": "cmd+shift+t", "command": "workbench.action.tasks.test" },
{ "key": "f5", "command": "workbench.action.debug.start" },
{ "key": "shift+f5", "command": "workbench.action.debug.stop" },
{ "key": "cmd+k cmd+r", "command": "clangd.restart" },
{ "key": "cmd+alt+.", "command": "editor.action.quickFix" },
{ "key": "cmd+e", "command": "editor.action.rename" }
]
Also learn from the default set:
Cmd+P— open file by name (fuzzy)Cmd+T— workspace symbolF12— go to definitionAlt+F12— peek definitionShift+F12— find referencesCmd+K Z— zen mode (focus)Cmd+Shift+O— file symbolsCmd+K F— close folder
The two-key clangd.restart binding is the single most useful shortcut
for learners — you’ll use it whenever compile_commands.json changes.
§10. Copilot / Cursor policy in one paragraph¶
Copilot OFF for Phases 0, 1, 2 (fundamentals). Turn back ON
selectively from Phase 3 onward via the github.copilot.enable map in §2.
Explicit rule: never accept a Copilot suggestion for a construct you
cannot re-derive from memory. First offence = disable Copilot for the
next 7 days. This is the same rule enforced in 00_command/README.md M1.
Cursor: acceptable as a secondary editor from Phase 4 for large
refactors. Do not switch to it as primary. clangd in Cursor has
historically been less predictable than in vanilla VSCode.
§11. Verifying the config — one 3-minute test¶
From ~/src/hello-cpp23 created in
01_day1_macos_setup.md §7:
Copy each of the six files from §3–§8 into the repo (in the right locations).
Open the folder in VSCode:
code ~/src/hello-cpp23.CMake Tools prompts to configure — accept. Ninja generator, Debug.
Wait for clangd to index (bottom-left status bar shows spinner).
Introduce a deliberate error — write
std::print("hi", ,);— you should see a red squiggly and Error Lens showing the message inline.Revert.
Cmd+Shift+Bbuilds.F5debugs. Set a breakpoint on theforline and step over it.
If all six steps work, your VSCode config is production-grade.
§12. Common issues after config¶
Symptom |
Fix |
|---|---|
clangd shows red squigglies on |
Verify §3 |
Format-on-save reformats the whole file |
Your |
CodeLLDB won’t launch: “couldn’t find |
Reinstall extension; on some macOS versions Apple Silicon build needs manual download; extension will offer the link |
clangd freezes on first index of large repo |
Set |
Format-on-save runs but nothing changes |
Check |