Emacs eglot with ESP-IDF

This is a short writeup about how we managed to use Emacs eglot mode work with ESP-IDF. This should also work equivalently with any other LSP client implementation in Emacs.

Procedure

Firstly, the clang toolchain for ESP-IDF should be installed by idf_tools.py install esp-clang. From this point, if the IDF_TOOLCHAIN environment variable is set to clang, then any freshly configured project will be configured to use the clang toolchain. When reconfiguring an existing project, it will be necessary to clean the build/ directory first.

From that point on, entering the ESP-IDF environment will place its own version of clangd on $PATH. idf.py build will generate a file titled compile_commands.json in the build directory, which is required by clangd to find important information in the project. This file should be symlinked this file to the project root, i.e ln -s build/compile_commands.json.

To make eglot or another LSP client call the correct version of clangd and see the correct libraries, a buffer-local environment has to be set before the mode starts. The best way we have found to do this is to use direnv in combination with the envrc Emacs plugin. Install and configure direnv on the system, and write a .envrc file in the project root which sources the ESP-IDF environment script. Additionally, enable envrc-global-mode in Emacs. With this configuration, Emacs will automatically load the ESP-IDF environment for the project, and eglot or other LSP clients will also operate correctly in this environment.

Discussion

When visiting ESP-IDF source files outside a directory configured with the ESP-IDF environment, eglot will misbehave because it is operating with the default system environment, which is undesirable. The solution we have been using for this is adding an .envrc file in the ESP-IDF directory, and symlinking a build_commands.json from any ESP-IDF project. This does not strike us as ideal, but it works well enough. There may be a better way, and possibly simpler to configure clangd by using a .dir-locals.el, but this has not yet been investigated in any depth.