Skip to content

NeoVim IDE Setup with clangd

If you prefer NeoVim over VS Code as your editor for ESP-IDF development, follow this guide to set it up with clangd for C/C++ intelligence.


Why NeoVim + clangd?

  • clangd is the same language server that powers VS Code's C/C++ extension — fast, accurate code completion, go-to-definition, and diagnostics
  • NeoVim is a lightweight, keyboard-driven editor that runs in the terminal — ideal for embedded development where you spend most time in idf.py and serial monitors
  • Works seamlessly inside the Docker dev environment

Step 1 — Install NeoVim

brew install neovim
sudo apt update
sudo apt install neovim

The Docker dev environment already includes NeoVim. If you need to install it manually:

apt-get update && apt-get install -y neovim

Verify the installation:

nvim --version
# Should show v0.9+ or later

Step 2 — Install clangd

clangd provides code intelligence (completion, diagnostics, go-to-definition) for C/C++.

brew install llvm
# clangd is included with LLVM
clangd --version
sudo apt install clangd
clangd --version
apt-get install -y clangd

The easiest way to get a full-featured NeoVim setup is using LazyVim, which comes with LSP support out of the box.

# Back up any existing config
mv ~/.config/nvim ~/.config/nvim.bak 2>/dev/null || true

# Clone LazyVim starter
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git

# Launch NeoVim — plugins will install automatically
nvim

After LazyVim installs, add the clangd LSP config:

mkdir -p ~/.config/nvim/lua/plugins
cat > ~/.config/nvim/lua/plugins/clangd.lua << 'EOF'
return {
  {
    "neovim/nvim-lspconfig",
    opts = {
      servers = {
        clangd = {
          cmd = {
            "clangd",
            "--background-index",
            "--clang-tidy",
            "--header-insertion=never",
            "--query-driver=/opt/esp/tools/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-elf/bin/xtensa-esp32s3-elf-g++",
          },
        },
      },
    },
  },
}
EOF

Query driver path

The --query-driver path must match the location of the Xtensa cross-compiler inside your Docker container or local ESP-IDF installation. Run the following to find it:

which xtensa-esp32s3-elf-g++
# Use the parent directory of the output in --query-driver

Step 4 — Generate compile_commands.json for clangd

clangd needs a compile_commands.json file to understand your project's include paths and compiler flags. ESP-IDF can generate this automatically.

In your ESP-IDF project directory:

idf.py reconfigure
# This generates build/compile_commands.json

Then symlink it to the project root so clangd can find it:

ln -sf build/compile_commands.json .

Automatic symlink

Add this to your project's CMakeLists.txt to auto-create the symlink on every build:

# Create compile_commands.json symlink at project root for clangd
execute_process(
  COMMAND ${CMAKE_COMMAND} -E create_symlink
  ${CMAKE_BINARY_DIR}/compile_commands.json
  ${CMAKE_SOURCE_DIR}/compile_commands.json
)

Step 5 — Useful Keybindings (LazyVim)

With LazyVim + clangd, these are the key bindings you'll use most:

Key Action
K Hover documentation
gd Go to definition
gr Find references
<leader>ca Code actions
<leader>cr Rename symbol
<leader>cd Line diagnostics
]d / [d Next / previous diagnostic
<C-o> / <C-i> Jump back / forward

Step 6 — Open Your ESP-IDF Project

cd /workspace/your-project
nvim main/your_main.c

clangd will index the project on first open (may take 30–60 seconds). After that, you'll get:

  • Autocompletion for ESP-IDF API functions (esp_err_t, gpio_config, etc.)
  • Go-to-definition into ESP-IDF headers
  • Inline diagnostics for compiler errors and warnings
  • Hover docs for function signatures

If clangd can't find ESP-IDF headers

Make sure compile_commands.json exists in the project root and the --query-driver path in your clangd.lua config points to the correct Xtensa compiler. Restart NeoVim after fixing either of these.


Quick Reference — NeoVim + ESP-IDF Workflow

# 1. Build and flash (from a separate terminal or tmux pane)
idf.py build flash monitor

# 2. Edit code (in NeoVim)
nvim main/main.c

# 3. Rebuild after edits
idf.py build

# 4. Use tmux for split workflow
tmux new -s esp
# Split: Ctrl+B %  (vertical) or Ctrl+B " (horizontal)
# One pane: nvim       |  Other pane: idf.py monitor