Skip to content

Instantly share code, notes, and snippets.

@ironlungx
Last active May 5, 2025 17:23
Show Gist options
  • Save ironlungx/a2b620de74f875c49c1d06999a8c41f8 to your computer and use it in GitHub Desktop.
Save ironlungx/a2b620de74f875c49c1d06999a8c41f8 to your computer and use it in GitHub Desktop.
PlatformIO with Neovim

Caution

I've moved this gist over to a template repository here. Everything here is unmaintained...

Extensions

Following are the extensions required for neovim:

I use lazy.nvim so, just add the following to your plugins table, or create a new file in lua/plugins/lsp.lua

return {
	{
		"williamboman/mason.nvim",
		config = function()
			require("mason").setup({})
		end,
	},
	{
		"williamboman/mason-lspconfig.nvim",
		config = function()
			require("mason-lspconfig").setup({
				ensure_installed = { "lua_ls", "clangd" },
			})
		end,
	},
	{
		"neovim/nvim-lspconfig",
		config = function()
			local lspconfig = require("lspconfig")
			lspconfig.lua_ls.setup({})
            		lspconfig.clangd.setup({}) -- Minimal setup
		end,
	},
}

For the LSP server setup (as recommended by @omani) this is what I use, it improves auto complete functionality

lspconfig.clangd.setup({
    on_attach = on_attach,
    capabilities = capabilities,
    cmd = {
      "clangd",
      "--background-index",
      "-j=12",
    },
})

Setting up the project

I've made a script to do all this for you:

$ cd path/to/the/project
$ curl -sSL https://gist.github.com/ironlungx/a2b620de74f875c49c1d06999a8c41f8/raw/script.sh | sh
$ rm compile_commands.json
$ pio run -t compiledb

BUT I DONT LIKE curl|sh:

  1. Initialize a project with pio init

  2. In the project root create the following files

    • .clangd: (thanks @omani for fixing and adding flags to the file)

      # clangd controls options for the LSP *server*
      CompileFlags:
        Add: [
          -DSSIZE_MAX,
          -DLWIP_NO_UNISTD_H=1,
          -Dssize_t=long,
          -D_SSIZE_T_DECLARED,
          -Wno-unknown-warning-option
        ]
        Remove: [
          -mlong-calls,
          -fno-tree-switch-conversion,
          -mtext-section-literals,
          -mlongcalls,
          -fstrict-volatile-bitfields,
          -free,
          -fipa-pta,
          -march=*,
          -mabi=*,
          -mcpu=*
        ]
      Diagnostics:
        Suppress: "pp_including_mainfile_in_preamble"
    • .clang-tidy: (thanks @omani for adding more flags!)

      Checks: '-*, -misc-definitions-in-headers' # Fixes some issue with header reinclusion	        
      IndentWidth: 2 # Only if you like 2, replace with 3/4/.. if you'd like        
    • gen_compile_commands.py:

      import os
      Import("env") # platformio specific stuff
      
      # include toolchain paths (i.e. to have stuff like the Arduino framework headers present in the compile commands)
      env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=True)
      
      # override compilation DB path
      env.Replace(COMPILATIONDB_PATH="compile_commands.json")
  3. Now add the following to your platformio.ini

    extra_scripts = pre:gen_compile_commands.py
  4. Now in your terminal run: pio run -t compiledb (Just delete the old compile_commands.json before executing this)

Hopefully this helped, I spent a lot of time figuring it out

#!/bin/sh
echo "This script is no longer maintained"
echo "Use this repo instead: https://github.com/ironlungx/nvim-pio"
@ironlungx
Copy link
Author

hi @omani, thank you so much for the corrections (I didn't know there were syntax errors, unless you are talking about the script - it is very likely there are syntax errors there). I will put your changes into the main gist.
Thanks again

@manuelfarzini
Copy link

Many thanks!

@blongworth
Copy link

Completely naive question: what file does the lspconfig live in?

@ironlungx
Copy link
Author

The lspconfig file is a lazy.nvim plugin. It's usually in the nvim config directory, on linux it is on ~/.config/nvim/ and on windows %USERPROFILE%\AppData\Local\nvim. Refer to this for structuring your lazy.nvim setup https://lazy.folke.io/usage/structuring. Hope this helps :)

This is the directory 'tree' for your neovim setup:

~/.config/nvim   OR   %USERPROFILE%\AppData\Local\nvim
├── lua
│   ├── config
│   │   └── lazy.lua
│   └── plugins
│       ├── lsp.lua
│       ├── plugin.lua
│       └── alpha.lua
└── init.lua

p.s. I've got a dotfiles repository with my neovim configuration (and other stuff), it might be useful to you

@samuelbles07
Copy link

Hi @ironlungx , thank you for providing this!

But somehow clangd cannot find the built in arduino libraries (eg. Wire, SPI) prompting Wire.h file not found. I see in generated compile_command.json there's nothing there mentioning path to built in arduino libraries. Is anyone having this problem too?

I tried adding something to platformio.ini but still nothings change.

[env:uno]
platform = atmelavr
board = uno
framework = arduino
lib_deps =
  Wire
build_flags = -Ilib -Isrc
extra_scripts = pre:gen_compile_commands.py

@samuelbles07
Copy link

For now i just add the missing path to the .clangd path

# clangd controls options for the LSP *server*
CompileFlags:
  Add:
    - -DSSIZE_MAX
    - -DLWIP_NO_UNISTD_H=1
    - -Dssize_t=long
    - -D_SSIZE_T_DECLARED
    - -Wno-unknown-warning-option
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/ArduinoOTA/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/AsyncUDP/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/BLE/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/BluetoothSerial/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/DNSServer/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/EEPROM/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/ESP32/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/ESPmDNS/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Ethernet/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/FFat/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/FS/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/HTTPClient/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/HTTPUpdate/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/LittleFS/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Insights/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/I2S/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/NetBIOS/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Preferences/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/RainMaker/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/SD_MMC/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/SD/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/SimpleBLE/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/SPIFFS/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/SPI/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Ticker/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Update/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/USB/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/WebServer/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/WiFiClientSecure/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/WiFi/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/WiFiProv/src
    - -I/Users/bles/.platformio/packages/framework-arduinoespressif32libraries/Wire/src

  Remove:
    - -mlong-calls
    - -fno-tree-switch-conversion
    - -mtext-section-literals
    - -mlongcalls
    - -fstrict-volatile-bitfields
    - -free
    - -fipa-pta
    - -march=*
    - -mabi=*
    - -mcpu=*

Diagnostics:
  Suppress:
    - pp_including_mainfile_in_preamble

@klineathan
Copy link

Thank you for making this! It's been a huge help. When including <Arduino.h>, I get the error "In included file: 'long type-name' is invalid". Any ideas how to fix?
image

@ironlungx
Copy link
Author

Hi @samuelbles07, could you try removing all the .clang* files and gen_compile_db.py file and curl the script again (i've made some changes, im not sure if it helps). If that doesn't work, whats the output of this

$ cd /dir/to/project
$ tree 
$ cat compile_commands.json

also, im pretty sure the build_flags = -Ilib -Isrc is redundant, ive never seen it before so maybe im wrong
and if possible, could you share your program?

Thanksss and sorry for the late reply

@omani
Copy link

omani commented May 4, 2025

update:
I made a simpler way to make include paths work. this way I don't have to spam my (in my case global) clangd config with -I include paths:

set dynamic build_flags in platformio.ini (by executing a script with !):

extra_scripts = pre:gen_compile_commands.py
build_flags = !python gen_includes.py

gen_includes.py script:

import os

def gen_recursive_include_path(dir):
    include_path = []
    for dir_path, dir_names, file_names in os.walk(dir):
        if "src" in dir_names:
            s = dir_path + "/src"
            include_path.extend([" -I", s])
    return include_path

flags = ""
flags = flags + "".join(
    gen_recursive_include_path(
        os.path.expanduser("~")
        + "/.platformio/packages/framework-arduinoespressif8266/libraries/"
    )
)
print(flags)

execute:

$ rm compile_commands.json ; pio run -t compiledb -v
$ $ grep -Ei libraries compile_commands.json -c
85

85 occurrences now in compile_commands.json :)

you can change the path ofc. in this example I've used the arduinoespressif8266 framework package.

with this setup I don't get any errors or warnings, Arduino.h works. ESP8266WiFi.h works, etc.

@ironlungx
Copy link
Author

Thanks for the contribution, I was thinking about moving this from a gist to a template repository on github, so you can make PRs and stuff, let me know if you all would be interested.

I don't fully get what you (@omani) are trying to do with the gen_includes.py script... I guess it's more full proof?

@omani
Copy link

omani commented May 4, 2025

I don't fully get what you (@omani) are trying to do with the gen_includes.py script... I guess it's more full proof?

the env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=True) in gen_compile_commands.py isolates the scope (see warning when running pio run -t compiledb -v.

when scoped, it does not find eg. the espressif8266 framework (which is located in $HOME/.platformio/). hence why for example @samuelbles07 had to import them manually here #5470163(comment).

this script includes all paths needed to have neovim LSP work without any errors.

@ironlungx
Copy link
Author

Oh okay... Would you like to make a PR on my repository for this?

@omani
Copy link

omani commented May 5, 2025

Oh okay... Would you like to make a PR on my repository for this?

sure

@ironlungx
Copy link
Author

https://github.com/ironlungx/nvim-pio 🙃
thank you very much for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment