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

.clangd

Configures the clangd language server

Repo root

.clang-format

Formatting rules — LLVM base

Repo root

.clang-tidy

Static analysis rule set

Repo root

.vscode/settings.json

Per-workspace VSCode settings

.vscode/

.vscode/tasks.json

Build / test / benchmark tasks

.vscode/

.vscode/launch.json

LLDB debug launch configs

.vscode/

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 the github.copilot.enable map.

  • clangd is the only C++ intelligence provider. We explicitly forbid ms-vscode.cpptools in 01_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 like auto foo() -> int.

  • readability-magic-numbers and cppcoreguidelines-avoid-magic-numbers — too noisy for numerical / algorithms code (Phase 2, Phase 5).

  • readability-identifier-length — fights every i, 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 symbol

  • F12 — go to definition

  • Alt+F12 — peek definition

  • Shift+F12 — find references

  • Cmd+K Z — zen mode (focus)

  • Cmd+Shift+O — file symbols

  • Cmd+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:

  1. Copy each of the six files from §3–§8 into the repo (in the right locations).

  2. Open the folder in VSCode: code ~/src/hello-cpp23.

  3. CMake Tools prompts to configure — accept. Ninja generator, Debug.

  4. Wait for clangd to index (bottom-left status bar shows spinner).

  5. Introduce a deliberate error — write std::print("hi", ,); — you should see a red squiggly and Error Lens showing the message inline.

  6. Revert. Cmd+Shift+B builds. F5 debugs. Set a breakpoint on the for line 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 <print>

Verify §3 -std=c++23 is present; restart clangd

Format-on-save reformats the whole file

Your .clang-format differs from what the file was authored to; either commit reformat or update .clang-format

CodeLLDB won’t launch: “couldn’t find codelldb

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 --pch-storage=memory (already in §2) and give it 60s; watch RAM usage

Format-on-save runs but nothing changes

Check [cpp] block in §2 has "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"