ElixirLS - the Elixir Language Server

Thanks!

Now don’t show the error message, but still having problems:

  1. When you start a big elixir app gives this error:

    [Error - 20:15:37] Elixir Language Server terminated abnormally because exited in: GenServer.call(#PID<0.125.0>, {:get, #Function<12.8708225/1 in ElixirLS.LanguageServer.Compilers.Elixir.compile_manifest/8>}, 5000)
    ** (EXIT) time out
    [Error - 20:15:37] GenServer ElixirLS.LanguageServer.Builder terminating
    ** (stop) exited in: GenServer.call(#PID<0.125.0>, {:get, #Function<12.8708225/1 in ElixirLS.LanguageServer.Compilers.Elixir.compile_manifest/8>}, 5000)
    ** (EXIT) time out
    (elixir) lib/gen_server.ex:737: GenServer.call/3
    (language_server) lib/language_server/compilers/elixir.ex:208: ElixirLS.LanguageServer.Compilers.Elixir.compile_manifest/8
    (language_server) lib/language_server/builder.ex:114: ElixirLS.LanguageServer.Builder.compile_elixir/2
    (elixir) lib/enum.ex:966: Enum.flat_map_list/2
    (elixir) lib/enum.ex:967: Enum.flat_map_list/2
    (language_server) lib/language_server/builder.ex:35: ElixirLS.LanguageServer.Builder.handle_call/3
    (stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
    (stdlib) gen_server.erl:647: :gen_server.handle_msg/5
    Last message: {:build, %{}}
    State: :ok
    [Info - 20:15:46] Connection to server got closed. Server will restart.
    [Info - 20:15:46] Child Logger.ErrorHandler of Supervisor Logger.Supervisor started
    Pid: #PID<0.76.0>
    Start Call: Logger.Watcher.watcher(:error_logger, Logger.ErrorHandler, {true, true, 500})
    Restart: :permanent
    Shutdown: 5000
    Type: :worker

  2. For phoenix projects it keep loading, show some call stack (very fast) and the debug auto stop

  3. For Elixir aplications (in this case a tcp listener) initialize and stop instantly but it logs the init functions

  4. The error checker on Phoenix 1.3 show this:

If need more information just tell me :smiley:

Greetings!

Just installed the VS Code extension, working fine for me so far.
Keep up the great work :smile:

1 Like

Great to hear! Thanks!

Ah, the automatic build and error reporting is by far the hackiest part of ElixirLS. I wrote this blog post about it: Compiler Hacks in ElixirLS. I want to tear it out and replace it.

Any chance you can share your repos with me to help me debug it?

Thank you for this project!
Like you I am very spoiled by great IDEs and that is the biggest pain point I have with elixir development right now.

I actually think if this becomes solid it can be a boost for elixir adoption. I know many devs that would skip elixir because of the dev experience right now.

Thanks a lot @JakeBecker, this works brilliantly and i agree with @samuba that better IDE integrations will boost elixir adoption. Im coming from the Javascript world and am very spoiled by IDE (Webstorm) and debugging support (Chrome dev tools). This was actually one of my biggest problems with elixir so far, it felt very unsafe to start a big project with a language that’s hard to debug … simply because i know how important it is to have great tools to dig into the code :slight_smile:

2 Likes

Thanks! Really appreciate the support :slight_smile: :thumbsup:

4 Likes

I share the same sentiment as @DominikGuzei, our startup nearly passed up adopting elixir and one of our chief concerns was debugging. In fact, you can see my suggestion to take advantage of VS Code here:

https://elixirforum.com/t/what-do-you-think-is-missing-or-needs-improving-in-elixir/2369/136

Our team is ecstatic about your work @JakeBecker, and feel strongly that this will increase elixir adoption. So thank you!

1 Like

@JakeBecker Any update on the support of umbrella projects?

Hi,

I’ve had ElixirLS on the backburner a bit, but I still intend to overhaul the build system entirely. I’ve had some support from Elixir’s core team on adding features to enable this, such as the --all-warnings option that made it into Elixir 1.5: https://hexdocs.pm/mix/1.5.1/Mix.Tasks.Compile.Elixir.html#module-command-line-options

However, I’ve been a bit blocked on adding additional changes to Elixir to help with ElixirLS by a PR that’s been in limbo for a month: https://github.com/elixir-lang/elixir/pull/6280

I’ve just pinged the core Elixir guys and hopefully they’ll get to it soon. Unfortunately, the earliest this stuff is all likely to work properly is with the release of Elixir 1.6, whenever that ends up being, since there’s a few changes that I want to make to the compilers so they can return their warnings and errors and I can remove the custom compiler from ElixirLS.

Sorry for leaving the build system a mess for the past month, but hopefully I’ll be able to release something more solid soon.

8 Likes

How hard could it be to use this with the new Atom IDE stuff? I would be glad to help and look into this.

3 Likes

(Cross-posted from my blog)

Some months ago, I released ElixirLS, an IDE “smartness” server for Elixir that powers a VS Code plugin. Now that Elixir 1.6 is looking fairly close to release, I figured it was time to update ElixirLS with some improvements — a new build system, code formatting, and an experimental, incremental Dialyzer server.

Rewritten build system

The initial release of ElixirLS included a fork of Elixir 1.4’s compiler with some really nasty hacks to get build warnings and errors to show up in the IDE. It didn’t work reliably at all — umbrella projects failed completely, for example. These hacks were necessary because there was no good way to get warning and error information from Mix.

I contributed some changes to Elixir 1.6 to report build diagnostics from compilers. The built-in compilers now conform to a new behaviour, Mix.Task.Compiler, and return their status and diagnostics in a standardized way. If third-party compilers implement this behaviour, their warnings and errors will show up automatically in ElixirLS too.

This let me completely remove the custom compiler from ElixirLS, and builds are much more reliable now. If you’re using Elixir >= 1.6, build diagnostics will show up automatically in the editor, but if you’re using a previous version, you’ll have to look at the text log from the extension to see build warnings and errors. Builds will run automatically when you save a file. If you want this to happen automatically when you type, turn on “autosave” in your IDE.

Code formatting

Elixir 1.6 includes a code formatter akin to Prettier for Javascript or gofmt for Go. It uses logic similar to inspect to format your code for a certain target line length (by default 100 characters). I’ve found it a great help, and it’s available in ElixirLS 0.2 if you’re using Elixir >= 1.6. Hit Shift + Alt + F in VS Code to format the current file. You can also set editor.formatOnSave to true if you want it to run every time you manually save a file.

Automatic, incremental Dialyzer

Dialyzer is a static-analysis tool for Erlang and Elixir. It’s very useful for catching errors having to do with type-checking, but it can be a pain to use. The analysis is notoriously slow, so programmers typically use a tool like Dialyxir to maintain a PLT file (“Persistent Lookup Table”) with the saved analysis of files that change infrequently, such as core Elixir libraries or project dependencies. Once a PLT file is generated, Dialyzer’s analysis can be acceptably fast.

But even with tools like Dialyxir, using Dialyzer isn’t as fast or easy as it could be. For example, you need to determine which core Erlang or Elixir libraries such as :ets or Mix to include when you build your PLT. If you forget to include them, Dialyzer can’t give you useful analysis when you use them in your code. Dialyzer also re-analyzes all the modules in your project every time, even if no modules they reference have changed.

I took a stab at addressing these shortcomings, and ElixirLS 0.2 includes an experimental, incremental Dialyzer server that runs after each successful build. It uses undocumented, internal Dialyzer APIs, and it’s only compatible with Erlang/OTP 20. Future Erlang changes might break it, but so far it seems pretty reliable when used with OTP 20. You can set elixirLS.dialyzerEnabled to false to disable it if it breaks.

The server keeps a manifest similar to a PLT file in .elixir_ls. After each build, it looks for modules that changed and only re-analyzes modules when necessary. It also looks at the abstract code in your compiled beam files to determine which core Erlang or Elixir modules you use. If it sees that you’re referencing a library that isn’t already analyzed, it includes it in the analysis automatically. That way, you never get “unknown module” warnings and you don’t have to manually specify which applications you reference. It just works, and after the initial analysis, it’s quite fast.

You can change the setting elixirLS.dialyzerWarnOpts to control which warnings are shown, though Dialyzer’s defaults are good for most projects. You can also set the module attribute @dialyzer to show or hide warnings at a module or function level.

I’m hoping that fast and easy static analysis can smooth over the problems that come from dynamic typing. Getting immediate feedback when you pass an argument of the wrong type or write an incorrect pattern-match can take a lot of the guesswork out of writing correct code.

Installing Elixir 1.6, Erlang/OTP 20, and ElixirLS

To take advantage of these new features, you’ll need Elixir 1.6 and Erlang/OTP 20. Since Elixir 1.6 isn’t released yet, you’ll need to install a pre-release version. I highly recommend installing both Elixir and Erlang from source using the tools kiex and kerl, respectively. That’ll let you use go-to-definition to jump from your code to the source code for core Elixir and Erlang modules. To install the latest pre-release Elixir with kiex, run kiex install master. To install ElixirLS, search “elixirls” in the extensions pane in VS Code. It’ll automatically use whatever Elixir and Erlang installations are the default in your shell.

Feedback is very welcome. Happy coding!

10 Likes

Has #8 elixir-ls requires elixir sources been tackled by this update, or will there still be restarts of the LS in the console, that do not give any clue to what the problem actually is?

But I’m happy you are back! I feared already that developement staled or was abondoned, since you didn’t answer in the issues for months now…

1 Like

This new release looks really good!

I have been trying to get this working with Spacemacs and have had some moderate success.

These are my steps so far:

Clone elixir-ls and build it with bash release.sh I also had to change apps/language_server/mix.exs to set embed_elixir to true. You want to make sure the language_server binary is available in your $PATH.

git clone https://github.com/emacs-lsp/lsp-mode some_dir/lsp-mode

Then I created this file in some_dir/lsp-elixir/lsp-elixir.el

(require 'lsp-mode)
(require 'elixir-mode)

(lsp-define-stdio-client 'elixir-mode "elixir" 'stdio #'(lambda () default-directory)
			 "Elixir Language Server"
			 '("language_server")
			 :ignore-regexps '("^langserver-elixir: reading on stdin, writing on stdout$"))

(provide 'lsp-elixir)

Add the following to dotspacemacs/user-config:

  (add-to-load-path "/path/to/some_dir/lsp-mode")
  (add-to-load-path "/path/to-some_dir/lsp-elixir")
  (with-eval-after-load 'lsp-mode
    (require 'lsp-flycheck))
  (require 'lsp-mode)
  (require 'lsp-elixir)
  (add-hook 'elixir-mode #'lsp-mode)

When you are in an elixir-mode buffer, you can type M+x then lsp-mode to enable it for the buffer. This is as far as I have got with it. You can verify it is working with M+x then lsp-capabilities. If you use flycheck, you can do M+x then flycheck-verify-setup

2 Likes

That’s awesome!

Embedding Elixir in the escript does work, but it locks ElixirLS at the Elixir release you built it with. My intention was that it’d use whatever Elixir version the user has installed. When you run release.sh, it should copy a script exscript.sh into the release directory. If you run ./exscript.sh language_server, the script looks for your system Elixir installation and sets ERL_LIBS before running the language_server escript. There’s an equivalent exscript.bat for Windows as well.

This approach doesn’t always work, and I’m going to look for a more robust way of doing it.

Apologies for ignoring your bug report for so long! I took a long break from the project after the initial release, but I’m going to look into your issue now.

You shouldn’t need source code available to run the language server, most likely the launch script is failing to set ERL_LIBS correctly. I’ll reply on the Github issue once I’ve investigated.

1 Like

Well, it works if sources are there, it doesn’t if they miss, even when set the correct ERL_LIBS by hand…

(Cross posting from my blog)

Announcing “ide-elixir”, an ElixirLS plugin for Atom IDE

Some time ago, I released ElixirLS, an IDE “smartness” server that powers a VS Code plugin. It runs in a separate process from the IDE and communicates via Microsoft’s frontend-agnostic Language Server Protocol.

Up until recently, pretty much only VS Code implemented the Language Server Protocol, but Atom announced support for it in September via the Atom IDE package. I’ve just released ide-elixir, an Atom IDE plugin powered by ElixirLS.

Features

It supports the big features from ElixirLS: Build diagnostics, auto-complete, code formatting, signature help, and automatic Dialyzer integration. Some of these features require Elixir 1.6 (which is still pre-release) or Erlang/OTP 20. I wrote about these features in my blog post for ElixirLS 0.2.

Atom IDE isn’t as mature as VS Code, but it’s getting there. Right now, the main features it’s missing are debugger support (which is technically not part of the Language Server Protocol), snippets in auto-complete, and an output log from the server. It seems slightly more prone to errors than VS Code, and the extension’s log messages are silently discarded, which can make it difficult to diagnose problems.

I don’t know whether debugger support is on the roadmap for Atom IDE. It’s not part of the Language Server Protocol, but it’s standardized in the similar Debug Adapter Protocol. Atom IDE’s UI is based on Nuclide, an IDE-like set of Atom extensions created by Facebook. Nuclide includes debugger support for several languages, so it’s possible that its UI could be merged into Atom IDE and integrated with the Debug Adapter Protocol. For the time being, though, you’ll need to stick to using VS Code to debug Elixir.

Installation

To install ide-elixir, first apm install either atom-ide-ui or nuclide, then install the ide-elixir package. Happy coding!

6 Likes

I switched some time ago to VS Code and started using the extension and I have to say, really good job @JakeBecker!

The feature that surprised me the most (since I thought it wouldn’t be useful) is probably the built-in dialyzer support that caught couple bugs for me, before I started running tests. The feature of automatically adding libraries to the analysis is also quite handy, though I wish it added some trace of that, since it means I probably need to add it in mix.exs to extra_applications.

Another very useful thing to have would be some test tasks to call it directly from the editor, it could integrate nicely with mix test --stale or running just a single test. I’m not sure, though, where would be place for this.

5 Likes

Glad you’re finding it useful!

It adds external libraries by actually walking the Erlang abstract forms and looking for function calls to modules that haven’t been analyzed yet, then doing a :code.which/1 to find them, so they’ll be added if they’re in your code path somewhere. Any modules outside of your project folder are assumed not to change unless your Elixir or Erlang version changes. It’s not based on extra_applications or anything in your mix.exs.

I do want to add support for tests, which will likely require some modest changes to the testing Mix task code. The Mix task loads your test .exs files using the new APIs in Kernel.ParallelCompiler which returns warning and error diagnostics, so with some tweaking it should be possible to get diagnostics to show up in your test files. mix test --stale already behaves similarly to a Mix compiler in that it keeps a manifest, so we can save warnings for tests to that manifest as we do for the other compilers. A hotkey to run stale tests would be good but likely requires some client-specific code, so it would probably be easier to have a configuration setting to run stale tests automatically following a successful build.

2 Likes