Supercharging VS Code with C++ Extensions
In a previous blog we demonstrated the most straightforward method to optimize Visual Studio Code for a Qt / C++ environment: simply let the tools do all the work! The example GitHub project we discussed automatically installs both the Microsoft C/C++ and clangd extensions into VS Code. You might wonder why you need both C++ extensions. The short answer is that having both will maximize your productivity in every situation… but read on for more detail.
The power of extensions
The thing that makes VS Code an amazing productivity tool is that you can add all kinds of extensions to it – tools to help you write, document, and debug code. While the C/C++ extensions from Microsoft and clangd do many of the same things, they each have individual strong suits.
A rather simple block diagram of our dual C++ extensions looks something like this.
VS Code with both the C/C++ and clangd extensions installed.
Extensions that use the language server protocol (LSP) can add feature support tailored to the programming language in use. LSP allows a “mere” editor application like VS Code to provide features like auto completion, identifier location, grammar-sensitive tips, parameter name hints, and refactoring operations, and both the Microsoft C/C++ extension and clangd use LSP.
However, you can also see that the Microsoft C/C++ extension uses a debug adaptor to connect to different compiler toolchains. This provides debugging functionality for all the debuggers that the C/C++ extension supports. That means that if you want to debug C++ code from within VS Code, you need the Microsoft C/C++ extension installed (or an equivalent C++ extension that has a debug adapter – something that clangd notably does not have).
Why not just Microsoft?
But if the Microsoft C/C++ extension provides both language support and debugging support, why do you need clangd at all?
There are a few small differences between the two in the refactoring or code actions that they offer, but the biggest difference is in speed.
For finding identifiers and taking you to their definition or their references, clangd is significantly faster than Microsoft. What is almost instantaneous with clangd takes a second or two with the Microsoft C/C++ extension, requiring a progress bar. Most developers spend more time reading code than writing it, and I almost always prefer enabling clangd because of this. Especially for developers who are frequently navigating big source trees, those little delays can be a minor annoyance that adds up.
Thankfully, you can have both extensions enabled, but disable the Microsoft C/C++ Intellisense features. That lets you use the faster clangd source navigation and still use the Microsoft extension when it comes to debugging your code.
When installing the clangd extension, it will automatically disable the Microsoft C/C++ Intellisense features for you. If it’s not the case, you can change this setting:
You do need a couple settings to make clangd work properly. These are automatically configured in our VS Code C++/Qt template but to do it by hand you’ll need the following:
- Insert the line
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)near the top of your CMakeLists.txt file or set the
CMAKE_EXPORT_COMPILE_COMMANDSenvironment variable to 1 in the environment where you will run CMake.
- Add this setting
- Insert the line
- to the .vscode/settings.json file in your project directory.
These two commands ensure cmake will capture the compiler commands that it needs to build the project during the configure stage. The file containing those commands is then copied to the root of the workspace where the clangd extension expects them. Without these two pieces, clangd won’t work properly.
You don’t need to do a full build to create and install your compile_commands.json file, you can just select the appropriate cmake preset on the bottom status bar. Alternatively, you can type Ctrl+Shift+P (Command+Shift+P on a Mac) to get the command palette and type “CMake: Configure”.
Using clangd configuration files
Everything said up to now is more than enough for most cases, but there are still some open issues we may need to take care of:
- Using clangd works out of the box when using a compilation database using the MSVC compiler, because clangd is able to pick correctly the right driver for clang from the compilation database. However, warning/error flags for MSVC are not always correctly mapped to clang ones. How can we get a more accurate code model in that case?
- Only one instance of clangd is running for each vscode window, so by default we can use only one
compile_commands.jsonfile. What happens if we are working in a multi-folder workspace, where each folder is an independently buildable application or library?
We can get to a solution for both cases by using clang configuration files.
Clangd provides a configuration mechanism starting from version 11, which allows among other things loading multiple
compile_commands.json files and customizing compilation flags for the code model. A complete documentation for this mechanism can be found here
To set up a clangd configuration file, you just have to create a
.clangd file in your project folder or one of its ancestors (e.g. if you’re working on two sibling folders, you should have the
.clangd file on their parent folder).
Then, to have VSCode’s clangd extension to pick up you configuration file, you should add
--enable-config among your clangd settings, like this:
"clangd.arguments": [ ... // "--enable-config" ]
Customize compilation flags through clangd configuration file
To customize compilation flags we need to use a
CompileFlags section in our configuration files. The section supports the following options:
Add: to append compilation flags to the compile commands
Remove: to remove compilation flags
CompilationDatabase: to specify a custom folder where to fetch the compilation database (
Compiler: allows to specify the compiler executable who needs to run (
Here’s an example to add custom flags:
CompileFlags: Add: [-Wall, -Wextras, -Wno-c++98-compat, -Wno-pre-c++17-compat]
And if you want to add specific flags for a given file or a given folder you can wrap the
CompileFlags section into an
If: PathMatch: .*/sourcesubdir/.*\.cpp CompilerFlags: Add: [...]
Working with a multi-folder workspace with clangd
Using the conditional
If section you can get one step ahead and configure clangd to correctly process all our source files in a multi-folder workspace.
First, you just set up the cmake configuration to generate a compiler database using the guide shown in the previous sections.
Then you set up the clangd configuration file to correctly map different compilation database with the correct subfolder in you workspace, as shown in the following example:
If: PathMatch: Project1/.* CompileFlags: CompilationDatabase: Project1 // the parent folder for your compile_commands.json // You can optionally customize specific compiler flags for Project1 here --- If: PathMatch: Project2/.* CompileFlags: CompilationDatabase: Project2 // You can optionally customize specific compiler flags for Project2 here
If your clangd extension doesn’t seem to be working properly, here’s a few troubleshooting tips:
- Confirm that compile_commands.json is in the root of the workspace directory. If it’s not, either use our GitHub example and instructions per this blog, or double-check the configuration settings in the above section.
- Make sure clangd indexing has been finished. For small projects, this happens nearly instantly. But for big projects, you might see a progress indicator in the bottom status bar that says, “indexing X/Y”. Wait until that goes away and try again.
- Restart clangd. This isn’t necessary too often, but if nothing else works, it’s worth a try: type Ctrl+Shift+P (or Command+Shift+P) and type “Restart Language Server”.
VS Code is an amazing tool and we’re always learning new ways to optimize our development environment. If you’ve got any related tips to share, leave a comment!