Most Cursor vs VSCode comparisons stop at the AI features. C++ developers hit a different wall first, and it is the extension registry. VS Code installs from Microsoft’s marketplace. Cursor installs from Open VSX. The Microsoft C/C++ extension (ms-vscode.cpptools) came back as “Extension not found” when I queried the Open VSX API on November 4, 2026, while clangd, CodeLLDB and CMake Tools all resolved.
The audience for this question is large. In the 2025 Stack Overflow Developer Survey, “Visual Studio Code was used by 75.9% of respondents, while Cursor was used by 17.9%.” Plenty of those Cursor users arrived from VS Code with a C++ project that suddenly has no IntelliSense, no debugger and a red wall of unknown cppdbg errors.
This tutorial builds a small C++20 project that behaves the same in both editors. You write a gap-check script that asks Open VSX what exists, install a registry-safe toolchain (clangd, CodeLLDB, CMake Tools), debug a ring buffer, and wire a weekly check into CI. If you only need to move a general extension list across, start with our walkthrough of VS Code extensions in Cursor. This guide goes narrower and deeper on one language.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What You Will Build, and Why C++ Splits the Two Editors
JavaScript and Python developers rarely notice which registry their editor uses, because the popular extensions publish to both. C++ is different. The language needs a language server, a debugger and a build integration, and the most familiar combination of those three has a Microsoft publisher name on each piece. When one piece is missing on the registry your editor reads, the failure is loud and confusing.
Microsoft’s own documentation for its PostgreSQL extension shows the split plainly. It lists the Visual Studio Marketplace as the install source for VS Code and the Open VSX Registry as the install source for Cursor. Cursor’s extension catalog is Open VSX based, so Microsoft-branded extensions can be absent, delayed or incompatible there. That is not a Cursor bug. It is a licensing and publishing choice made by the extension owners.
The practical answer is to stop depending on the pieces that live in only one registry. Three open-source extensions cover the whole C++ loop and run in both editors:
- clangd (
llvm-vs-code-extensions.vscode-clangd) for completion, diagnostics, navigation and clang-tidy. - CodeLLDB (
vadimcn.vscode-lldb) for breakpoints, stepping and variable inspection. - CMake Tools (
ms-vscode.cmake-tools) for presets, configure, build and CTest.
By the end you will have a repository with a tested ring buffer, a .vscode folder that both editors read, a script that measures registry gaps, a shared AI rules file for each editor, and a scheduled workflow that fails when a required extension disappears. The whole build takes about 55 minutes if your compiler and CMake are already installed.
One scope note on the Cursor vs VSCode question. This guide does not argue that Cursor or VS Code is the better editor. The AI layer differs a lot and you will see a short comparison near the end. The point here is narrower: make your C++ toolchain independent of which editor opens the folder, so the editor choice stays a matter of taste and budget.
Prerequisites and Versions
You need a machine that can compile C++20 and both editors installed. The table lists the minimums. Where a number comes from my own run, I say so. Where it comes from a project’s release page, I hedge, because release trackers disagree for a day or two after a launch.
| Tool | Minimum | Check command | Notes |
|---|---|---|---|
| VS Code | 1.144 or newer | code --version | Release trackers such as Releasebot list a 1.145 build on November 4, 2026 |
| Cursor | Latest stable | cursor --version | Free plan is enough for this tutorial |
| Python | 3.9 or newer | python3 --version | Standard library only, I ran 3.12.3 |
| CMake | 3.25 | cmake --version | Preset schema version 6 needs 3.25 |
| Ninja | 1.11 | ninja --version | Any recent Ninja works |
| C++ compiler | GCC 12 or Clang 16 | g++ --version | Any recent GCC or Clang install that meets the minimum works |
| clangd binary | LLVM 17 or newer | clangd --version | The extension can also download its own copy |
| Git | 2.30 | git --version | Only needed for the CI step |
On Windows, install a compiler through MSYS2 or Visual Studio Build Tools, then add Ninja and CMake. The VS Code C++ documentation notes that MSYS2 needs 64-bit Windows 8.1 or newer. On macOS, Xcode command line tools give you Clang and LLDB. On Debian or Ubuntu, sudo apt install build-essential cmake ninja-build clangd covers everything except the editors.
Skim the VS Code to Cursor migration guide first if you have never opened Cursor before. It explains the import prompt that copies your VS Code settings. This tutorial assumes that step is done or irrelevant.
Time budget: steps 1 to 4 take about 15 minutes, steps 5 to 7 about 15 minutes, steps 8 to 11 about 20 minutes, and the last two steps about 5 minutes. Everything runs offline except the Open VSX queries and the extension installs.
Steps 1 and 2: Check Versions and Install Both Shell Commands
Step 1 is a version sweep. The gap-check script in step 5 shells out to your editors, so both commands must exist. Run the checks in one terminal and keep the output, because version drift between editors is the first thing you will debug later.
python3 --version
cmake --version
ninja --version
g++ --version
code --version
cursor --version
If code or cursor prints command not found, move to step 2. Both editors can register a shell command from the Command Palette. In VS Code, open the palette and run Shell Command: Install ‘code’ command in PATH. In Cursor, run the matching Shell Command: Install ‘cursor’ command entry. On Linux the command usually exists after a package install. On Windows the installer adds both to PATH, so open a new terminal after installing.
Step 2 confirms the commands can list extensions, because the script depends on that exact flag pair:
code --list-extensions --show-versions
cursor --list-extensions --show-versions
Each line prints in the form publisher.name@version. Save one sample for each editor into a text file now. The script can read a file with the prefix file:, which lets you test the logic without either editor present. That also makes the script usable on a CI runner where neither editor is installed.
Why the version output matters
Cursor is built on the VS Code codebase, but it ships on its own schedule and reads a different registry. Two machines with the same project can run different clangd or CMake Tools versions without anyone noticing. The listing above is your baseline. When something breaks in one editor only, compare the two listings before you touch any config.
Keep the first listing in your notes. A diff of two listings from different days is the cheapest regression test you will ever run for an editor setup, and it takes ten seconds.
Steps 3 and 4: Create the Ring Buffer Project and CMake Presets
Step 3 creates a small but real C++ target: a fixed-size ring buffer. It exercises templates, std::optional, a header-only class and a test, which is enough surface for clangd and the debugger to prove themselves. Create the folder and the header:
#pragma once
#include <array>
#include <cstddef>
#include <optional>
template <typename T, std::size_t N>
class RingBuffer {
public:
bool push(const T& value) {
if (size_ == N) {
return false;
}
data_[(head_ + size_) % N] = value;
++size_;
return true;
}
std::optional<T> pop() {
if (size_ == 0) {
return std::nullopt;
}
T value = data_[head_];
head_ = (head_ + 1) % N;
--size_;
return value;
}
[[nodiscard]] std::size_t size() const { return size_; }
[[nodiscard]] bool full() const { return size_ == N; }
private:
std::array<T, N> data_{};
std::size_t head_ = 0;
std::size_t size_ = 0;
};
The driver pushes six values into a buffer that holds four, then drains it. Two pushes must be rejected, and the pops must come back in insertion order:
#include <iostream>
#include "ring_buffer.hpp"
int main() {
RingBuffer<int, 4> buffer;
for (int i = 1; i <= 6; ++i) {
const bool ok = buffer.push(i * 10);
std::cout << "push " << i * 10 << (ok ? " ok" : " rejected (full)") << '\n';
}
while (const auto value = buffer.pop()) {
std::cout << "pop " << *value << '\n';
}
return 0;
}
Compile and run it once without any editor. If this fails, no extension can save you, so fix the compiler first. I built it with g++ -std=c++20 -Wall -Wextra -Wpedantic, with no warnings, and got this output:
push 10 ok
push 20 ok
push 30 ok
push 40 ok
push 50 rejected (full)
push 60 rejected (full)
pop 10
pop 20
pop 30
pop 40
Step 4 adds the build description. A CMakeLists.txt defines the app and a test target, and turns on CMAKE_EXPORT_COMPILE_COMMANDS so clangd can see the exact flags used for every file:
cmake_minimum_required(VERSION 3.25)
project(cpp_parity LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(app src/main.cpp)
target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic)
enable_testing()
add_executable(test_ring_buffer tests/test_ring_buffer.cpp)
target_compile_options(test_ring_buffer PRIVATE -Wall -Wextra -Wpedantic)
add_test(NAME ring_buffer COMMAND test_ring_buffer)
Presets keep both editors and the terminal on the same configure and build settings. Without presets, CMake Tools in VS Code and CMake Tools in Cursor each remember their own kit choice, and the two drift. A preset file lives in the repository, so there is nothing to remember:
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
}
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" }
],
"testPresets": [
{ "name": "debug", "configurePreset": "debug", "output": { "outputOnFailure": true } }
]
}
The test itself uses plain assert so the project needs no dependency download. Put this in tests/test_ring_buffer.cpp:
#include <cassert>
#include <iostream>
#include "../src/ring_buffer.hpp"
int main() {
RingBuffer<int, 3> buffer;
assert(buffer.push(1) && buffer.push(2) && buffer.push(3));
assert(!buffer.push(4));
assert(buffer.full());
assert(buffer.pop().value() == 1);
assert(buffer.push(4));
assert(buffer.pop().value() == 2);
assert(buffer.pop().value() == 3);
assert(buffer.pop().value() == 4);
assert(!buffer.pop().has_value());
std::cout << "all ring buffer tests passed\n";
return 0;
}
Compile it with g++ -std=c++20 -o t tests/test_ring_buffer.cpp and run ./t once. It should print all ring buffer tests passed. A common mistake is compiling the test with -DNDEBUG in a release preset, which strips every assert and makes the test pass vacuously. The debug preset above avoids that.
Step 5: Audit Open VSX With a Gap-Check Script
This is the step that separates a working setup from a lucky one. Instead of guessing which extensions exist for Cursor, ask the registry. The Open VSX web API answers GET https://open-vsx.org/api/{namespace}/{name} with a JSON document that includes the latest version, a timestamp and a verified flag. For a missing extension it returns an error object, and the script treats that as a gap.
The script below reads your editor’s installed list (from code, cursor or a saved file), checks each wanted extension against Open VSX, and prints a table. It exits with code 1 only when a required extension is missing, so it works as a CI gate. Save it as tools/cpp_gap_check.py:
#!/usr/bin/env python3
"""Compare the C/C++ extensions you need against Open VSX and your editor."""
import argparse
import json
import subprocess
import sys
import urllib.error
import urllib.request
API = "https://open-vsx.org/api/{ns}/{name}"
# id -> (role, required)
WANTED = {
"llvm-vs-code-extensions.vscode-clangd": ("language server", True),
"vadimcn.vscode-lldb": ("debugger (LLDB)", True),
"ms-vscode.cmake-tools": ("CMake build and test", True),
"twxs.cmake": ("CMake syntax", False),
"xaver.clang-format": ("formatter", False),
"ms-vscode.makefile-tools": ("Makefile support", False),
"ms-vscode.cpptools": ("Microsoft IntelliSense + cppdbg", False),
}
def fetch(ext_id):
ns, name = ext_id.split(".", 1)
req = urllib.request.Request(API.format(ns=ns, name=name),
headers={"User-Agent": "cpp-gap-check/1.0"})
try:
with urllib.request.urlopen(req, timeout=20) as resp:
data = json.load(resp)
except urllib.error.HTTPError as err:
if err.code == 404:
return None
raise
if "error" in data:
return None
return {
"version": data.get("version"),
"date": (data.get("timestamp") or "")[:10],
"verified": data.get("verified"),
}
def installed(source):
if source.startswith("file:"):
text = open(source[5:], encoding="utf-8").read()
else:
text = subprocess.run([source, "--list-extensions", "--show-versions"],
capture_output=True, text=True, check=True).stdout
out = {}
for line in text.splitlines():
line = line.strip()
if "@" in line:
ext, ver = line.rsplit("@", 1)
out[ext.lower()] = ver
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--source", default="file:/dev/null",
help="'code', 'cursor' or 'file:PATH' with 'id@version' lines")
ap.add_argument("--json", action="store_true")
args = ap.parse_args()
have = installed(args.source)
rows, failed = [], False
for ext_id, (role, required) in WANTED.items():
info = fetch(ext_id)
mine = have.get(ext_id.lower(), "-")
if info is None:
status = "MISSING" if required else "absent"
failed = failed or required
elif mine not in ("-", info["version"]):
status = "DRIFT"
else:
status = "ok"
rows.append({"id": ext_id, "role": role, "required": required,
"openvsx": info["version"] if info else "-",
"published": info["date"] if info else "-",
"installed": mine, "status": status})
if args.json:
print(json.dumps(rows, indent=2))
else:
print(f"{'extension':40} {'open vsx':9} {'published':11} {'installed':10} status")
for r in rows:
print(f"{r['id']:40} {r['openvsx']:9} {r['published']:11} {r['installed']:10} {r['status']}")
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()
The WANTED dictionary is the policy. Required entries are clangd, CodeLLDB and CMake Tools. The Microsoft C/C++ extension sits there as optional, so the report shows it as absent instead of failing the build. Change the booleans to match your team’s rules.
Run it and read the output
I fed it a sample extension list (clangd deliberately set one version behind) and ran it on November 4, 2026. The registry numbers below came live from Open VSX, and the installed column came from my sample file:
$ python3 tools/cpp_gap_check.py --source file:code-extensions.txt
extension open vsx published installed status
llvm-vs-code-extensions.vscode-clangd 0.6.0 2026-05-21 0.5.2 DRIFT
vadimcn.vscode-lldb 1.12.3 2026-08-23 1.12.3 ok
ms-vscode.cmake-tools 1.24.42 2026-09-14 1.24.42 ok
twxs.cmake 0.0.17 2020-08-17 - ok
xaver.clang-format 1.9.0 2021-11-25 - ok
ms-vscode.makefile-tools 0.12.17 2025-02-26 - ok
ms-vscode.cpptools - - 1.35.5 absent
$ echo $?
0
Read the status column from the top. ok means the installed version matches the registry. DRIFT means your editor holds a different version than Open VSX, so either you are behind or the editor’s mirror is. absent means an optional extension has no listing, and MISSING means a required one does not. The sample file lists [email protected] as installed because a VS Code machine would have it, yet Open VSX has no such listing. That row is the whole reason this tutorial exists.
For tooling, switch on --json. It prints a list of objects with the keys id, role, required, openvsx, published, installed and status. Pipe it into jq or a dashboard. Our earlier Cursor vs VS Code extension parity audit takes the same idea across a whole editor profile instead of one language, and the two scripts combine well.
How the script works
The script has four small parts, and each one is worth understanding before you adapt it. The fetch function builds the API URL from the extension ID by splitting on the first dot, so vadimcn.vscode-lldb becomes namespace vadimcn and name vscode-lldb. It sends a custom User-Agent, because anonymous default agents sometimes get throttled by public services. A 404 returns None. Any other HTTP error raises, which is deliberate: a 500 from the registry should not be misreported as a missing extension.
The installed function accepts either an editor command or a file. For an editor it runs --list-extensions --show-versions and parses each publisher.name@version line. It lowercases the IDs, because the marketplace treats them case-insensitively and the CLI output sometimes differs in case from the ID you typed. The main function then walks the policy dictionary, assigns one of four statuses, and sets the exit code. Only a missing required extension flips failed to true.
Notice what the script does not do. It never downloads an extension and it never installs anything. It only reads. That keeps it safe to run on a shared CI runner and makes it easy to review in a pull request. If you later add write behavior, such as auto-installing missing pieces, put it in a separate script so the audit stays read-only.
Step 6: Install One Extension Set in Both Editors
With the audit done, the install list writes itself. Everything marked ok and required goes in. The table shows what the live query returned for the candidates a C++ developer usually reaches for. These are my own measurements from the step 5 script and a few extra IDs, taken on November 4, 2026, so treat the numbers as a snapshot.
| Extension ID | Job | On Open VSX? | Version seen | Published |
|---|---|---|---|---|
| llvm-vs-code-extensions.vscode-clangd | Language server | Yes | Not listed | Not listed |
| vadimcn.vscode-lldb | LLDB debugger | Yes | 1.12.3 | 2026-08-23 |
| ms-vscode.cmake-tools | CMake build and test | Yes | 1.24.42 | 2026-09-14 |
| eclipse-cdt.cdt-gdb-vscode | GDB debugger | Yes | Not listed | 2026-07-29 |
| xaver.clang-format | Formatter wrapper | Yes | 1.9.0 | Not listed |
| ms-vscode.makefile-tools | Makefile support | Yes | 0.12.17 | Not listed |
| ms-vscode.cpptools | Microsoft IntelliSense and cppdbg | No (not found) | None | None |
| ms-vscode.cpptools-extension-pack | Microsoft bundle | No (not found) | None | None |
Two details deserve attention. First, CMake Tools on Open VSX sat at 1.24.42 while a GitHub issue on Microsoft’s repository referenced 1.25.5, so the registry can trail the source project. Second, several rows show old publish dates, such as the CMake syntax and formatter wrappers. An old date is not a defect, since those extensions are stable, but it tells you not to expect new features from them.
Now install the set, which is where the Cursor vs VSCode difference finally disappears from your daily work. Put the loop in a script so a new teammate runs one command per editor. Save this as scripts/install-extensions.sh:
#!/usr/bin/env bash
set -euo pipefail
EDITOR_CMD="${1:-cursor}"
EXTS=(
llvm-vs-code-extensions.vscode-clangd
vadimcn.vscode-lldb
ms-vscode.cmake-tools
xaver.clang-format
)
for ext in "${EXTS[@]}"; do
"$EDITOR_CMD" --install-extension "$ext" --force
done
"$EDITOR_CMD" --list-extensions --show-versions | grep -Ei 'clangd|lldb|cmake|clang-format'
Run ./scripts/install-extensions.sh code and then ./scripts/install-extensions.sh cursor. The final grep prints what landed, and the output should show four lines in each editor. If one editor prints three, run the gap check again and look for MISSING.
Also commit a workspace recommendation file so both editors prompt anyone who opens the folder. The unwantedRecommendations entry matters in VS Code, because it stops the editor from nagging you to install the Microsoft extension that conflicts with clangd:
{
"recommendations": [
"llvm-vs-code-extensions.vscode-clangd",
"vadimcn.vscode-lldb",
"ms-vscode.cmake-tools",
"xaver.clang-format"
],
"unwantedRecommendations": [
"ms-vscode.cpptools"
]
}
Verify the install by opening src/main.cpp, hovering over RingBuffer, and checking that a type tooltip appears. No tooltip means the language server has not started yet, so open the Output panel and select clangd from the dropdown. The next step configures it.
Step 7: Configure clangd, clang-tidy and the Compile Database
clangd is the piece that replaces Microsoft’s IntelliSense engine. It parses your code with a real Clang front end, so it needs to know the compiler flags for each file. The compile_commands.json that CMake exports provides them. Configure the project once so the file exists:
cmake --preset debug
ls build/compile_commands.json
Then give clangd a project-level config file. A .clangd file in the repository root is read by the language server regardless of which editor launched it, which is exactly the property you want. It points at the build folder and turns on a focused set of clang-tidy checks:
CompileFlags:
CompilationDatabase: build
Diagnostics:
ClangTidy:
Add: [bugprone-*, performance-*, modernize-*]
Remove: [modernize-use-trailing-return-type]
The Remove line drops modernize-use-trailing-return-type, a check that rewrites every function signature and annoys most teams. Add or remove entries as your style guide requires. Keep the list short at first, because each check costs analysis time on every keystroke.
Editor-specific behavior goes in .vscode/settings.json. Both editors read this file. Cursor accepts the same keys as VS Code because it shares the settings schema, and the settings sync tutorial covers how to keep user-level settings aligned across the two. For this project, the workspace file sets clangd flags and CMake behavior:
{
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=never",
"--completion-style=detailed",
"--query-driver=/usr/bin/g++*,/usr/bin/clang++*"
],
"C_Cpp.intelliSenseEngine": "disabled",
"cmake.useCMakePresets": "always",
"cmake.configureOnOpen": true,
"editor.formatOnSave": true,
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format"
}
}
Each clangd argument earns its place. --background-index builds a project-wide symbol index while you work. --clang-tidy turns on the checks from .clangd. --header-insertion=never stops completion from adding includes behind your back. --completion-style=detailed shows full signatures. --query-driver tells clangd which compiler binaries it may ask for the standard library include paths, and without it you meet the classic 'iostream' file not found squiggle when building with GCC.
The line C_Cpp.intelliSenseEngine only matters in VS Code when the Microsoft extension is installed. In Cursor the key is unknown, so the settings editor may underline it, which is harmless. Keeping the line means a VS Code user who installs the Microsoft extension by habit still gets clangd as the single source of diagnostics instead of two engines arguing. If you want a deeper look at how editors talk to language servers, our language server extension tutorial builds one from scratch.
Reload the window, open main.cpp, and introduce a typo such as buffer.pushh(1). A red squiggle with a clangd message should appear within a second or two. Undo the typo before you move on.
Steps 8 and 9: Wire Up CMake Tools and CodeLLDB Debugging
Step 8 connects the editor to the build. With cmake.useCMakePresets set to always, CMake Tools reads CMakePresets.json and shows the debug preset in the status bar. Pick it, and the extension configures into build/. Because the preset enables CMAKE_EXPORT_COMPILE_COMMANDS, clangd refreshes its database right away. For reference, the CMake 4.5 release notes page was still showing a development snapshot on November 4, 2026, so pin your minimum to 3.25 and test newer versions in CI instead of assuming them.
Add a task so a debug session can build first. The task runs the same two commands you would type in a terminal, which keeps results identical across editors and the shell:
{
"version": "2.0.0",
"tasks": [
{
"label": "build-debug",
"type": "shell",
"command": "cmake --preset debug && cmake --build --preset debug",
"group": { "kind": "build", "isDefault": true },
"problemMatcher": ["$gcc"]
}
]
}
Step 9 is the debugger. CodeLLDB speaks the Debug Adapter Protocol and ships its own LLDB build on most platforms, so you rarely need a system debugger for basic use. The launch configuration uses the type lldb, not cppdbg:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug app (CodeLLDB)",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/build/app",
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "build-debug"
}
]
}
Most C++ launch examples online use "type": "cppdbg", which belongs to the Microsoft extension. In Cursor that type has no debugger behind it, so the session fails with a message about an unknown debug type. Swapping to lldb is the whole fix, and the configuration works in VS Code too. Our debugging launch.json tutorial goes deeper on tasks and compound launches.
Debug the ring buffer
- Open
src/main.cppand click the gutter next to the line that callsbuffer.push(i * 10). - Press F5 and choose Debug app (CodeLLDB). The pre-launch task builds the debug preset first.
- When the breakpoint hits, expand the Variables panel. You should see
bufferwithhead_at 0 andsize_at 0 on the first stop. - Press F5 to continue through five more stops. On the fifth iteration
size_reads 4, which is the buffer capacity, and the push returns false. - Hover over
okafter stepping over the call. It should readfalseon iterations five and six, matching the two rejected lines in the program output.
If the breakpoint shows as a hollow circle and never hits, the binary lacks debug symbols. That happens when the active preset is a release configuration. Check the status bar and switch back to debug.
Steps 10 and 11: Format on Save, Lint and Run CTest
Step 10 covers formatting and linting. The settings file already sets editor.formatOnSave and picks xaver.clang-format as the C++ formatter, which wraps the clang-format binary. Install that binary through your package manager if clang-format --version fails, since the extension does not ship one. Then add a style file so every machine formats identically:
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
Save a file with messy indentation to test it. The editor should reflow it to four-space indents with a 100-column limit. For linting, the --clang-tidy flag from step 7 already runs the checks you listed in .clangd. Diagnostics from those checks show in the Problems panel with the check name in brackets, such as bugprone- or performance- prefixes, so you can silence a noisy check by name.
Step 11 runs the tests. CMake Tools adds a test status item to the bottom bar and lists CTest targets in the Testing sidebar when you use the standard test explorer view. From a terminal, the three preset commands do the same work:
cmake --preset debug
cmake --build --preset debug
ctest --preset debug
The test preset sets outputOnFailure, so a failing assertion prints its message instead of a bare failure line. A passing run ends with CTest reporting that all tests passed, and the single registered test is named ring_buffer. If you want richer test UI integration or plan to write your own runner, the Test Explorer extension tutorial shows how the Testing API works under the hood.
Break something on purpose to see the failure path. Change the capacity check in push from size_ == N to size_ > N and rerun the tests. The fourth assertion, assert(!buffer.push(4)), fails, the process aborts, and CTest marks the test as failed. Revert the change before continuing.
Steps 12 and 13: Share AI Rules and Gate the Gap Check in CI
Step 12 is the AI layer, and it is where the editors honestly differ. Cursor reads project rules from .cursor/rules. VS Code with GitHub Copilot reads .github/copilot-instructions.md. Give both the same C++ conventions so an agent in either editor builds with presets and runs CTest. Here is the Cursor version, which includes frontmatter that scopes the rule to C++ files:
---
description: C++20 conventions for this repo
globs: src/**/*.cpp,src/**/*.hpp,tests/**/*.cpp
alwaysApply: false
---
- Build with `cmake --preset debug && cmake --build --preset debug`, then run `ctest --preset debug`.
- Target C++20. Do not add third-party dependencies without asking.
- Keep headers self-contained and mark value-returning accessors [[nodiscard]].
- Never edit files under build/ and never hand-edit compile_commands.json.
- Format with the repo .clang-format. Do not reformat files you did not change.
Copy the bullet lines, without the frontmatter, into .github/copilot-instructions.md. Keeping one source of truth matters more than the file names, and the shared AI rules tutorial shows how to generate both from a single file.
The AI products moved quickly this autumn. Cursor’s changelog shows new models arriving in September, among them Claude Sonnet 5.5, Claude Opus 5.5 and Grok 4.7, and an Agent SDK entry dated October 20. On the VS Code side, Microsoft published a post on configurable Copilot suggestion eagerness on October 26, available to paid Copilot subscribers. Neither change touches your C++ toolchain. That is the point of building the toolchain on clangd and CodeLLDB: the AI choice stays independent.
Step 13 automates the audit. Registries change under you. An extension can be unpublished, a publisher can rename, or a mirror can fall behind. A scheduled workflow runs the gap check every Monday and on demand, and the job fails if a required extension disappears from Open VSX:
name: editor-gap-check
on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
gap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python3 tools/cpp_gap_check.py
Because the script uses only the standard library, the job needs no dependency install. The default source reads /dev/null, so it checks registry availability only. Add a second job on a developer laptop or self-hosted runner if you also want installed-version drift.
The finished project
Here is the final tree. Every file appears above, and the C++ builds clean with warnings enabled:
cpp-parity/
CMakeLists.txt
CMakePresets.json
.clangd
.clang-format
.gitignore
.cursor/rules/cpp.mdc
.github/copilot-instructions.md
.github/workflows/editor-gap-check.yml
.vscode/extensions.json
.vscode/settings.json
.vscode/tasks.json
.vscode/launch.json
scripts/install-extensions.sh
src/main.cpp
src/ring_buffer.hpp
tests/test_ring_buffer.cpp
tools/cpp_gap_check.py
Commit it, push it, and run the workflow manually from the Actions tab. A green run means all three required extensions are live on Open VSX today. Treat a red run as a prompt to read the table, not as a crisis, since the usual cause is a temporary registry error that clears on the next run.
Cursor vs VS Code for C++: Side by Side
With the toolchain neutral, the real differences are the registry, the AI layer and the price. This table compares them using what I measured and what the vendors state. Cursor’s tiers come from its pricing page, which lists Plus at $8 a month, Premium at $30 and Super at $100 for new customers, alongside a Free plan. Legacy plans reportedly remain for existing subscribers.
| Area | VS Code | Cursor | What to do |
|---|---|---|---|
| Extension registry | Visual Studio Marketplace | Open VSX (per Microsoft’s own extension docs) | Check availability with the step 5 script |
| Microsoft C/C++ (cpptools) | Available | Not found on Open VSX in my check | Use clangd as the shared engine |
| clangd | Works | Works | Install in both |
| LLDB debugging | CodeLLDB works | CodeLLDB works | Use type lldb in launch.json |
| GDB debugging | cppdbg or cdt-gdb-vscode | cdt-gdb-vscode (found on Open VSX) | Avoid cppdbg in shared configs |
| CMake integration | CMake Tools | CMake Tools (registry can trail) | Pin presets in the repo |
| Editor price | Free | Free plan, then $8, $30 or $100 a month | Pick by AI usage, not C++ needs |
| Project AI rules | .github/copilot-instructions.md | .cursor/rules/*.mdc | Keep one shared bullet list |
The VS Code C++ documentation page still points users at the Microsoft extension. Research for this article also found that Microsoft lists a newer C/C++ DevTools extension in its marketplace, which its license text reportedly describes as pre-release. I could not confirm its status on Open VSX, so I left it out of the audit list. Add its ID to WANTED once you have verified it yourself.
My read on Cursor vs VSCode for C++: if your team standardizes on clangd, CodeLLDB and CMake Tools, the editor becomes interchangeable for C++ work. You pay for Cursor only if its agent features save you more time than the subscription costs, and you can keep VS Code installed as a free fallback for the days a Cursor limit or outage gets in the way.
Which editor fits which C++ job
- Firmware and embedded work with GDB stubs. Either editor works if you stay on the CDT GDB adapter. VS Code keeps the option of Microsoft’s debugger, so it has the wider menu, and Cursor has the narrower but sufficient one.
- Large CMake codebases with heavy refactors. Cursor’s agent features can speed up repetitive edits, and clangd handles navigation in both. Keep the build and test commands in rules files so an agent never invents its own.
- Classrooms and locked-down laptops. VS Code costs nothing and needs no account. Cursor’s Free plan costs nothing too, but a school policy about AI tools may decide for you.
- Teams with mixed editors. The shared
.vscodefolder,.clangd, the presets and the weekly gap check let people choose freely without splitting the toolchain.
None of these rows is a verdict on quality. They describe where each editor’s strengths show once the registry gap is closed. If you read one thing from this section, make it the last bullet: a team that agrees on files in the repository can argue about editors forever without breaking a build.
Common Pitfalls
These are the mistakes that cost the most time when a C++ project moves between the two editors.
- Running two language engines at once. VS Code with both the Microsoft extension and clangd shows duplicate or conflicting diagnostics. Disable IntelliSense in the Microsoft extension, or uninstall it, and let clangd own diagnostics.
- Skipping the compile database. clangd without
compile_commands.jsonguesses flags and misreads includes, defines and the language standard. Always export it from CMake and point.clangdat the folder. - Forgetting
--query-driveron GCC. clangd asks the compiler for its system include paths only when the binary matches the pattern you allow. A mismatch causes missing standard header errors on valid code. - Copying
cppdbglaunch configurations. Tutorials written for the Microsoft debugger fail in Cursor. Use thelldbtype, or install the GDB adapter from Open VSX for GDB workflows. - Assuming Cursor mirrors Open VSX instantly. Cursor’s forum carries bug reports about its marketplace trailing the upstream registry. When a new release matters, compare versions with the gap check before blaming your config.
- Sideloading Microsoft extensions from random sites. Licensing aside, malicious builds do appear. Cursor published a security notice about malicious versions 2.1.312 and 2.1.313 of the Anthropic.claude-code extension in its extension marketplace. Install from registries, and pin versions you have reviewed.
- Hard-coding absolute paths. A
--query-driverpath that works on your laptop breaks for a teammate on macOS. Use patterns, or keep platform-specific values in user settings. - Debugging a release build. Optimized binaries have no useful line mapping. Keep the debug preset active before you press F5, and look at the status bar instead of guessing.
- Mixing kits and presets. CMake Tools still supports interactive kits, but a repo that carries both a preset file and a team habit of picking kits ends up with two build directories and two compile databases. Choose presets and set
cmake.useCMakePresetstoalways.
A final pitfall belongs to teams. When one person publishes an internal extension to the Microsoft marketplace only, everyone on Cursor loses it. The guide to publishing extensions to Open VSX shows how to ship to both registries from one pipeline.
Troubleshooting
Each item names the symptom first, then the fix. Work from the top, because the early items cause the later ones.
- Symptom:
Extension 'ms-vscode.cpptools' not foundwhen installing in Cursor. The extension has no Open VSX listing. Install clangd and CodeLLDB instead, and run the gap check to confirm both resolve. - Symptom: clangd reports
'iostream' file not found. Add a--query-driverpattern that matches your compiler path, then reload the window. Confirm the path withwhich g++. - Symptom: no completions and an empty clangd output panel. The language server never started. Check that the binary exists with
clangd --versionand thatclangd.pathis not set to a stale location. - Symptom:
build/compile_commands.jsonis missing. Runcmake --preset debugand confirm the preset setsCMAKE_EXPORT_COMPILE_COMMANDS. Ninja and Makefile generators support the export, while some IDE generators do not. - Symptom: breakpoints stay hollow. The binary has no debug symbols. Switch to the
debugpreset, rebuild, and check that the program path inlaunch.jsonpoints tobuild/app. - Symptom:
cursor: command not found. Install the shell command from the Command Palette, then open a new terminal. On macOS confirm that the folder it installs to is on PATH. - Symptom: CMake Tools ignores the presets. Check that CMake is 3.25 or newer, that
cmake.useCMakePresetsisalways, and that the preset file is valid JSON. A trailing comma silently disables presets. - Symptom: clangd pins a CPU core for minutes after opening the folder. The background index is building. Add
-j=4toclangd.argumentsto cap workers, and let the first index finish once. - Symptom: format on save does nothing. Install the
clang-formatbinary, confirm the default formatter ID for[cpp], and check that a.clang-formatfile exists in a parent folder. - Symptom: attaching to a process is denied on Linux. Yama restricts ptrace. For attach sessions only, set
kernel.yama.ptrace_scopeto 0 temporarily, or launch under the debugger instead. - Symptom: the CI gap check fails with a network error. Open VSX returned a transient failure. Rerun the job. If it repeats, add a retry loop in the script and keep the exit code strict only for real 404 responses.
Advanced Tips
Add a sanitizer preset for AddressSanitizer and UBSan
A second configure preset gives you memory-error checking without touching the main debug flow. It inherits the debug preset, writes to its own build folder and adds the sanitizer flags. Build and run the ring buffer with -fsanitize=address,undefined -fno-omit-frame-pointer, and clean code should print the same output as the plain build:
{
"name": "asan",
"inherits": "debug",
"binaryDir": "${sourceDir}/build-asan",
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer"
}
}
Add that object to the configurePresets array and pick it from the CMake Tools status bar when you hunt a crash. Point clangd at the matching folder with --compile-commands-dir=build-asan only while you work in that preset, because the database it reads must match the build you debug. CodeLLDB handles sanitizer-instrumented binaries fine, and the sanitizer report appears in the debug console.
Run GDB and LLDB side by side
Some embedded targets and remote debug stubs speak GDB only. The CDT GDB adapter (eclipse-cdt.cdt-gdb-vscode) is one option for them, but confirm it is available in your Open VSX registry before relying on it. Install it next to CodeLLDB and add a second launch configuration with type gdb. Check the extension’s README for the exact attribute names, since they differ from the Microsoft schema. Keep both configurations in the repository so a teammate can switch debuggers from the dropdown.
Harden the audit against bad publishers
Every API response in my run carried verified set to true, which signals that the namespace has a verified owner. Extend the script so a required extension whose verified flag turns false fails the build. That one-line change would catch a namespace takeover or a lookalike publisher, the kind of risk behind the malicious extension incident Cursor warned about. Pair it with pinned versions in your install script, for example publisher.name@version on the install command line.
Install from a reviewed VSIX file
When a registry lags and you must have a specific version, download the VSIX from the publisher’s own release page, read its changelog, and install it with cursor --install-extension path/to/file.vsix. Do not pull Microsoft’s proprietary extensions out of its marketplace to run them in other editors, since their license terms may not allow it. Ask the publisher to release on Open VSX instead.
If your C++ library also ships Python bindings, the Python setup for Cursor and VS Code covers the language server choices on that side, which follow the same registry logic you just applied.
Frequently Asked Questions
Is Cursor vs VSCode a real choice for C++ work?
Yes, once you build on clangd, CodeLLDB and CMake Tools. The registry gap only bites when your setup depends on Microsoft-published extensions that Open VSX lacks. Close that gap and the choice comes down to AI features and price.
Does the Microsoft C/C++ extension work in Cursor?
Not through the registry route I tested. The Open VSX API returned no listing for ms-vscode.cpptools on November 4, 2026. Install clangd and CodeLLDB, which resolve on Open VSX and give you completion, diagnostics and debugging.
Is clangd a good replacement for Microsoft IntelliSense?
For CMake projects with a compile database, yes. clangd uses a real Clang parser, so it matches what your compiler sees. The trade-off is setup. IntelliSense can work with looser project descriptions, while clangd wants exact flags and a working compile_commands.json.
Can I debug with GDB in Cursor?
Yes, through the CDT GDB adapter on Open VSX, or through CodeLLDB if LLDB is acceptable. Avoid cppdbg in shared launch files, because that type depends on the Microsoft extension.
What does DRIFT mean in the gap-check output?
It means your installed version differs from the version Open VSX serves. The cause is either an outdated editor or a mirror that has not caught up. Update the extension, rerun the script, and see whether the status returns to ok.
Do I need to pay for Cursor to follow this tutorial?
No. Every step runs on the Free plan, and VS Code is free. Paid Cursor tiers, which start at $8 a month for Plus on the pricing page, matter for heavy AI usage and not for the C++ toolchain.
Does this setup work on Windows?
Yes, with two changes. Install a compiler through MSYS2 or Visual Studio Build Tools, and write the --query-driver pattern with Windows paths, for example C:/msys64/ucrt64/bin/g++.exe. The Python script and the presets run unchanged.
Where can I read more about the editor tooling choices?
Our software coverage hub collects the editor, framework and developer tooling guides in one place, and the other Cursor and VS Code tutorials there follow the same 13-step format.





