How about Clangd instead of IntellliSense?
June 3, 2024

I have long heard that Clangd is better than IntelliSense. But what does that actually mean better? It is, after all, just an opinion. I decided to check it out. In the following text, I describe how I swapped IntelliSense for Clangd and what the results were.

While working on a project on nRF with Zephyr using Visual Studio Code, we got an idea: How about swapping IntelliSense for Clangd? Is it worth a try? In theory, they offer the same thing, just in a different way. I decided to see if it was actually worth it.

We substitute IntelliSense for Clandg in the VSC on the Zephyr RTOS.

IntelliSense -> Clang

Here we go.

1) Add clangd plugin configuration to settings.json:

/// CLANGD settings:
    "C_Cpp.intelliSenseEngine": "disabled",
	"clangd.path": "/usr/bin/clangd", //if clangd is not in PATH
	"clangd.arguments": [
            "--pretty",
            "--enable-config",
            "--background-index",
            "--compile-commands-dir=${workspaceFolder}/build",
            "--query-driver=${env:ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-*",
			// "--log=verbose",
			"--log=error"
    ],
"clangd.checkUpdates": false
/// CLANGD settings^

2) Still a project configuration file in the root directory of the project. The file must be named ".clangd" and its contents are:

CompileFlags:
  Add: -Wno-unknown-warning-option
  Remove: [-mfp16-format=ieee, -fno-reorder-functions ]
Diagnostics:
  UnusedIncludes: Strict
  Suppress:
    - drv_unknown_argument

3) Install the clangd plugin in your VSC:

Clangd in VSC

The plugin should install clangd if it does not find it in the PATH, or in the directory specified in step 1 ("clangd.path": "/usr/bin/clangd").

4) Install the clangd plugin in your VSC:

Now enjoy autocomplete, path recognition, object information and code checking while editing.
And it's in a pretty nice form for the developer:

Clangd in battle


Troubleshooting

Here we present an example of a configuration for nRF and Zephyr processors, but with some minor adjustments, this approach will work for other cases as well.

When working with Zephyr, it's a good idea to add an additional configuration file that is global in scope. File .clangd placed in the root directory of the project will act on all files below it in the directory structure. You can have different configurations for different directories by simply placing separate files in them .clangd. However, once you start viewing Zephyr source files outside of your project, this configuration no longer applies. For example, when you open a file kernel.h From the Zephyr catalog, errors may occur.

Clangd a coverage problem

All because clangd does not understand our project's compiler arguments, and there is no ".clangd" file above the "kernel.h" file that tells it to ignore them. The global configuration file is called something else for some reason: "config.yaml". Its location depends on the operating system:

  • Windows: %LocalAppData%%clangdconfig.yaml;
  • Linux: ~/.config/clangd/config.yaml;

Copy the contents of your ".clangd" file to "config.yaml" and the problem will be solved.

You can quickly access both configuration files in Visual Studio Code by pressing [CTRL+SHIFT+P] and typing "clangd: configuration."

Add Clangd configuration

In order for an external tool to do well in analyzing C/C++ code, it must "know" the same thing as the compiler. This means knowing how each file is compiled (with what flags, inclusions, etc.) and the "internals" of the toolchain. We provide this information by providing:

  • The path to compile_commands.json (In parameter: --compile-commands-dir=${workspaceFolder}/build);
  • Compiler/toolchain (in parameter: --query-driver=${env:ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-* - thanks to the "*" clangd will choose by itself whether it should be arm-zephyr-eabi-gcc or g++);

Of course, the paths can be specified as absolute, but in the future you have to remember to update them if you change the toolchain or output directory.

You can also do the configuration of the clangd plugin through the GUI:

In the ".clangd" file, we can also suppress errors, even for individual files or groups. Example:

Example of error suppression

Of course, remember: "Don't shoot the messenger."

Important Note

In the case of our project based on NRF SDK v2.6.1, the latest clangd (v1.8.3) dropped "...arm-zephyr-eabi/12.2.0/include/" from the list of included directories. This made it impossible to find some Zephyr header files (error: In included file: 'stddef.h' file not found). So I recommend installing version e.g. 17.0.3 - in this version we did not have this problem.

What to do if something doesn't work?

1. switch the login level from --log=error at --log=verbose (in the clangd plugin configuration or in settings.json).

2. open some .c/.cpp file so clangd has something to analyze.

3. open the "Output" of the clangd plugin:

4. restart clangd by pressing [CTRL+SHIFT+P] and typing "clangd: restart":

5. see for a start if clangd gets the correct parameters:

Also check the paths in the log and compare those with compile_commands.json.

And that's it.

‍Summary.


After switching from IntelliSense to Clangd, we noticed several significant benefits:

  • ‍More effectivelanguage understanding: Clangd has a better understanding of the structure and hierarchy of the code, which allows for more precise analysis.
  • Better support for hierarchies: You can more easily see the structure and dependencies in the code.
  • Faster hints and automation: Clangd immediately informs you about missing inclusions or automatically adds them, which speeds up your work.

These changes have definitely streamlined our workflow and improved our coding efficiency.

Clangd has a lot of fans at GoodByte, so maybe you too will take a day or two to get familiar with it.
It's a really cool tool. Seriously.