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 and provide it with the new configuration.
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..