I use Doom Emacs as my main coding editor, and eglot for language server shenanigans. My config is mainly optimized towards Elixir, so for Ruby I was mostly using the default Doom’s Ruby module. It worked pretty well for me.
The main hurdle was always projects not using Rubocop. I prefer StandardRB, because I don’t like bike-shedding, and their defaults are really good for me. But in the absence of .rubocop.yml file, Doom tried to use default Rubocop settings for linting, instead of detecting StandardRB. In the past, I worked around this by replacing rubocop-mode with standard-mode, but recently Doom’s maintainer decided to go full-on with Ruby LSP for formatting and linting, so I had to re-h…
I use Doom Emacs as my main coding editor, and eglot for language server shenanigans. My config is mainly optimized towards Elixir, so for Ruby I was mostly using the default Doom’s Ruby module. It worked pretty well for me.
The main hurdle was always projects not using Rubocop. I prefer StandardRB, because I don’t like bike-shedding, and their defaults are really good for me. But in the absence of .rubocop.yml file, Doom tried to use default Rubocop settings for linting, instead of detecting StandardRB. In the past, I worked around this by replacing rubocop-mode with standard-mode, but recently Doom’s maintainer decided to go full-on with Ruby LSP for formatting and linting, so I had to re-hack it.
Fortunately, Ruby LSP supports addons and there is one for StandardRB. Unfortunately, it’s not as easy as it sounds…
tl;dr
You need to put this in your .dir-locals.el:
((ruby-mode
. ((eglot-server-programs
. ((ruby-mode . ("ruby-lsp"
:initializationOptions
(:formatter "standard"
:linters ["standard"]
:enabledFeatures (:codeActions t
:diagnostics t
:formatting t)))))))))
How I got there
Using .dir-locals.el is what eglot suggests in its documentation. However, the suggested form did not work for me:
((nil
. ((eglot-workspace-configuration
. (:rubyLsp
(:formatter "standard" :linters ["standard"]
:enabledFeatures (:codeActions t :diagnostics t :formatting t)))))))
After examining the eglot-events-buffer, I realized that eglot was sending workspace/didChangeConfiguration event with my new config, including my chosen formatter and linter. But apparently, Ruby LSP server was not doing anything with that message.
Indeed, after going through the code, I found that workspace/didChangeConfiguration is not a supported message by Ruby LSP! I offered to add it and spent few days on implementation, only to find out that it would not help me. Why?
The issue here is that, upon initializing the language server connection, Ruby LSP checks for supported formatters and finds nothing. After that it, sets documentFormattingProvider to null, effectively telling the client that it does not support formatting. Period. eglot notes it down and whenever the user requests formatting, it just ignores the request, knowing it won’t be fulfilled.
So, the next task after implementing support for workspace/didChangeConfiguration would be to add server dynamically registering new capabilities, formatter in this case. While it sounded promising and after I did most of the work, I unfortunately realized that eglot does not support dynamic registrations :( I scraped the code and looked for another solution…
… which, in this case, is to simply shut down the LS connection and reinitialize it with standard formatter and linter from the start. At first I wasn’t really happy about it, but then I figured this is actually simple and efficient solution, much in line with Erlang’s philosophy of killing and restarting things, rather than using elaborate reconfiguration or healing techniques.
In any case, I now have a working formatter and linter and know a lot more about language servers.
Next stop: making StandardRB support region formatting (Rubocop does not support it either).