Coming to Elixir (and VSCode) from Java/Kotlin (and IntelliJ IDEA) was a big shock!

Almost a year ago I’ve decided to change things a bit and accepted an offer from a company whose whole stack (100+ microservice) is written in Elixir. After being spoiled for years by JetBrains’ tools, using VSCode as my primary IDE (let’s call it that) felt awful the first couple of days (or even weeks). Of course, not having a proprietary language server means there will be some issues and my understanding of the new tooling was not the best so the initial struggle was normal. However, as weeks passed, I got used to most of it and got a better understanding of my new development environment, but there were still things that didn’t work as I would’ve liked.

And, as I usually do in similar situations, I’ve started looking into ways of fixing those issues.

Fixing ElixirLS initial issues

During the first week at my job I went through a couple of posts on how to set up VSCode for Elixir development, I installed ElixirLS extension, disabled automatic fetching of dependencies since that is known to break Elixir language server (ElixirLS) if turned on, and I thought this will be enough for everything to “just work”. However, I soon realized that even manual fetching of dependencies or even just a VSCode restart will sometimes break ElixirLS. Problems that I’ve usually experienced ranged from “Go to Definition” not working on the project-level, VSCode reporting errors that are not there (non-existent module or function for modules or function that exist), and auto-completion not working at all.

Fix for that is simple - either delete .elixir_ls folder and restart VSCode or, if that doesn’t help, delete .elixir_ls, deps, and _build folders, fetch deps and restart VSCode. Restarting VSCode was needed to restart the language server, but since version 0.10.0 there’s a built-in command in VSCode to restart language server without restarting VSCode. Also, I’ve realized I can see language server output in VSCode’s output panel by selecting ElixirLS from the dropdown. This helped me understand if the language server is having issues or if it’s still indexing the project.

Now, there was only one thing left…

Fixing “Go to Definition” for standard library

When the language server was working “Go to Definition” worked perfectly for project-level files and any dependencies that were added in mix.exs. Even the ones from the local filesystem. But it didn’t work for any module or function in the standard library. And this was something I absolutely love in IntelliJ - inspecting code from the standard library of the language you are using gives you a great insight into how things work under the hood. This is especially useful when you are working with a new language! At that point, I tried ElixirLS for IntelliJ, but after setting it up, most of the things didn’t work so I’ve decided not to use it. Another reason was that I was actually trying to reduce the memory footprint of my IDE since I usually have ~5 projects opened at the same time and running a resource-heavy IDE is not ideal. However, IntelliJ’s “Go to Definition” worked for the standard library!

Now, to set up any language support in IntelliJ you have to point it to an SDK (usually, Java is set up by default, but you still have to add multiple JDKs manually if you need them) and this actually gave me the idea – why is it not possible to point ElixirLS to source files? I’ve installed Elixir using asdf and the installation folder contains source code, but it’s not being picked up.

After some googling, I’ve come across a couple of posts that said this can be done by simply compiling Elixir from the source. Usually, this would be fine, but having Elixir installation managed by asdf, compiling it manually felt wrong so I’ve checked asdf-exlixir’s README.

It turns out asdf-elixir is using precompiled version when you execute asdf install elixir <version> which does not contain source code. However, if you take any commit reference (SHA string) from Elixir repo and use asdf install elixir ref:<commit_reference>, asdf will actually build it from the source to which that reference points. Each Elixir release is tagged so finding a commit reference for a correct version is pretty straightforward. After that, just as with normal asdf installation, you have to execute asdf global elixir ref:<commit_reference> to set that version as global.

After this is done, go to any project, delete .elixir_ls, deps and _build, run VSCode again and wait for the language server finish its job. Now, “Go to Definition” should work for everything (for the standard library it actually takes a couple of seconds; others should be instant).

Support for other IDEs (or text editors)

I was using VSCode in the examples above assuming that’s what most people are using, but this “fix” does not have anything to do with VSCode itself. It’s a “fix” for ElixirLS so it will work in any IDE (or text editor) that uses it (e.g. Neovim with built-in LSP).