Rust analyzer
Rust analyzer is a language server which provides editor support for the Rust programming language. Neovim has built-in language server protocol support.
Cargo supports feature flags, which enable conditional configuration. However, any blocks which are not compiled for a given feature do not benefit from rust-analyzer language server support.
While it is possible to enable features at startup in Neovim configuration, an ideal solution would be to define a user command, say :FtSet
, which would set the feature list to the provided arguments.
For example, to set features a
and b
we would like to run :FtSet a b
.
If you just want the function, you can find it here. If you are also interested in some Neovim LSP implementation details, read on!
Required setup
To set up rust-analyzer, it is convenient to use the neovim/nvim-lspconfig
package, which you can install by following the instructions in the repository.
You also must have the rust-analyzer binary available on your PATH
or you can provide a custom binary during configuration (as detailed here).
Basic feature configuration
When enabling rust-analyzer with vim.lsp.enable('rust_analyzer')
, it is possible to configure rust-analyzer to enable (or disable) features:
vim..
However, assuming you have set this up in global configuration like init.lua
, it is rather inconvenient to change the features which are enabled while editing a file.
Dynamic feature flags
Instead, we want to implement a function to set feature flags dynamically at runtime.
Modifying the existing configuration
In order to update rust-analyzer, we first need to access the existing configuration so that we can modify it without altering other settings.
The built-in way to access running language servers is with the vim.lsp.get_clients
function.
This function accepts an optional dictionary which can be used to filter the clients which are returned.
In our case, we would use vim.lsp.get_clients({ name = "rust_analyzer" })
.
Since this could match multiple clients, this returns an array.
To access the running client, we can index into the array (by default, lua arrays start at 1
).
To view the configuration of the currently running rust-analyzer server, we would therefore run
:lua= vim.lsp.get_clients({ name = "rust_analyzer" })[1].config
In order to update the configuration, we just need to modify the configuration, and then send the updated configuration to rust-analyzer.
For example, to set features a
and b
, we can do something like:
local rustAnalyzerSettings = vim....
-- if rust-analyzer is not running at all, this will be `nil`
if rustAnalyzerSettings ~= nil
The structure of the rustAnalyzerSettings
object is the same as the object which we initially configured using vim.lsp.config
.
Propogating the feature flags to rust-analyzer
Now, the local rustAnalyzerSettings
object contains a copy of the configuration, with the cargo.features
value updated.
However, in order for our configuration update to take place, we must propagate the settings to the (currently running) rust-analyzer instance.
The language server protocol has a specific notification type called workspace/didChangeConfiguration
.
This is a notification that is sent by the client to the server.
Neovim allows you to send these notifications in lua code with vim.lsp.buf_notify
.
In principle, the following should work:
let client = vim..
if client ~= nil
However, at the time of writing this article, I was unable to make this work.
It seems that rust-analyzer and Neovim do not support dynamic updates of workspace/didChangeConfiguration
.
A hint that this is the case can see this by looking at the ‘capabilities’ section of rust-analyzer:
:lua= vim.lsp.get_clients({ name = "rust_analyzer" })[1].capabilities.workspace.didChangeConfiguration
We can see here that the dynamicRegistration
option is false
.
As far as I can tell, rust-analyzer by default only requests configuration on startup from Neovim.
Workaround: restart rust-analyzer on feature change
A somewhat unsatisfying but functional workaround is simply to restart rust-analyzer.
local rustAnalyzerSettings = vim....
if rustAnalyzerSettings ~= nil
All that remains is to wrap this in a user command.
vim..
Here, opts.fargs
is an array of all of the arguments passed to the command :FtSet
.
We explicitly set nargs = '*'
to set an arbitrary number of features simultaneously.
You can read more about nvim_create_user_command
in the docs.
Function definitions
Here are a few examples demonstrating some operations. A few things that would be great to fix:
- The operations do not de-duplicate the feature list.
- The implementation is quite repetitive.
- The arguments should suggest completions from a list of features read from
Cargo.toml
. - You should put these function definitions somewhere that is only enabled when rust-analyzer attaches to the Neovim instance.
Set features
vim..
Set all features (--all-features
)
vim..
List enabled features
vim..