-- Zephyr RTOS support. -- -- This extra is driven entirely by Zephyr's own environment variables and is -- inert unless one of them is set, so it costs nothing on a machine that does -- no Zephyr work and needs no per-host editing. -- -- ZEPHYR_BASE the zephyr repo -- presence of this enables the extra -- ZEPHYR_SDK_INSTALL_DIR Zephyr SDK root (toolchain variant "zephyr") -- GNUARMEMB_TOOLCHAIN_PATH toolchain root (variant "gnuarmemb") -- ZEPHYR_TOOLCHAIN_VARIANT which of the above is in use -- -- Zephyr is otherwise "C + CMake + Kconfig + devicetree", so clangd and cmake -- do the heavy lifting. What clangd cannot work out unaided is the built-in -- include paths of a cross compiler, hence --query-driver. -- (Zephyr's build/compile_commands.json is found by clangd on its own: it -- searches ./ and ./build of each parent directory.) local zephyr_base = os.getenv("ZEPHYR_BASE") local sdk_dir = os.getenv("ZEPHYR_SDK_INSTALL_DIR") local gnuarmemb = os.getenv("GNUARMEMB_TOOLCHAIN_PATH") -- Not a Zephyr environment -> contribute nothing. if not (zephyr_base or sdk_dir or gnuarmemb) then return {} end --- Toolchain globs for clangd's --query-driver, taken from the Zephyr vars. --- Falls back to whatever cross gcc the environment puts on $PATH when the --- toolchain variables are not set (as with a distro-packaged toolchain). ---@return string[] local function query_driver_globs() local globs, seen = {}, {} local function add(p) if p and p ~= "" and not seen[p] then seen[p] = true globs[#globs + 1] = p end end -- Zephyr SDK: /-zephyr-elf/bin/-zephyr-elf-gcc if sdk_dir then add(sdk_dir .. "/*/bin/*-gcc") end -- gnuarmemb variant: /bin/arm-none-eabi-gcc if gnuarmemb then add(gnuarmemb .. "/bin/*-gcc") end -- No toolchain var set: resolve the cross compiler through $PATH instead of -- assuming a prefix, and glob its real directory. if #globs == 0 then for _, cc in ipairs({ "arm-zephyr-eabi-gcc", "arm-none-eabi-gcc", "riscv64-zephyr-elf-gcc", "xtensa-espressif_esp32-elf-gcc", }) do local exe = vim.fn.exepath(cc) if exe ~= "" then add(vim.fs.dirname(vim.uv.fs_realpath(exe) or exe) .. "/*-gcc") end end end return globs end return { -- Syntax for the two Zephyr-specific languages { "nvim-treesitter/nvim-treesitter", opts = { ensure_installed = { "devicetree", "kconfig" } }, }, { "neovim/nvim-lspconfig", -- Filetype detection Neovim does not ship with init = function() vim.filetype.add({ extension = { overlay = "dts", -- board/shield overlays are devicetree }, filename = { ["prj.conf"] = "kconfig", ["west.yml"] = "yaml", }, pattern = { ["Kconfig.*"] = "kconfig", -- Kconfig.foo, Kconfig.defconfig, ... [".*/boards/.*%.conf"] = "kconfig", [".*/prj_.*%.conf"] = "kconfig", [".*_defconfig"] = "kconfig", }, }) end, opts = function(_, opts) opts.servers = opts.servers or {} local clangd = opts.servers.clangd or {} local drivers = query_driver_globs() if #drivers > 0 then clangd.cmd = clangd.cmd or { "clangd" } table.insert(clangd.cmd, "--query-driver=" .. table.concat(drivers, ",")) end -- Treat a west workspace / Zephyr app as a project root. clangd.root_markers = clangd.root_markers or {} vim.list_extend(clangd.root_markers, { "west.yml", ".west", "prj.conf", "CMakeLists.txt" }) opts.servers.clangd = clangd return opts end, }, }