Sometime in the early part of this year, I set up Emacs to use Rustic with lsp-mode and rust-analyzer for a much better Rust experience… or so I thought. See, my previous setup (using rust-mode and the Rust Language Server) didn’t work very well apart from simplistic highlighting, so switching to Rustic already made the experience much better. I assumed immaturity in the Emacs LSP support was why I could never get the renaming or code actions to work.

Until today, when I came across a post on users.rust-lang.org which mentioned an interesting facet of rust-analyzer’s syntax highlighting:

Rust-analyzer has semantic highlighting based on the parsed ast [sic]. It shows mutable variables with an underscore. It knows about the format! syntax. It has a token type for unresolved identifiers. It marks dereferences of raw pointers with an unsafe attribute.

I hadn’t seen any of this in action! I opened a Rust file and checked the faces being used, before and after forcing an update to the relevant modes, but there was no sign of this supposed semantic highlighting, even though my configuration set lsp-semantic-highlighting to t. My first port of call, therefore, was the lsp-mode website. On the settings page, I saw a reference to lsp-enable-semantic-highlighting, so I changed my config to set that instead. (Later, I found out that the latter replaced the former.) No change. I verified from the Rustic documentation that I wasn’t misconfiguring anything. Next, I looked at the lsp-mode source code. I used the invocations I saw there to check whether the language server I was connected to supported semantic highlighting. That gave me an empty (i.e. negative) response. I tried the same function with a different feature and got a detailed response, so I took that to mean lsp-mode thought the server didn’t support semantic highlighting.

Since I knew from the aforementioned post that rust-analyzer does support semantic highlighting and saw that it was added recently (for some value of recent) I thought perhaps my version was too old. It turned out I had installed the binary manually, so I deleted that file and used rustup component add rust-analyzer-preview to make it automatic from now on. When I restarted Emacs, however, I saw no change. Looking at the *lsp-log* buffer, I noticed it couldn’t find the rust-analyzer binary, probably because it was installed under a different path. I used this to pick up the path from rustup:

(setq lsp-rust-analyzer-server-command (list (substring (shell-command-to-string "rustup which rust-analyzer") 0 -1)))

Upon restarting Emacs, lsp-mode still couldn’t find the binary. I saw the value of the variable was reset to ("rust-analyzer") in the Rust buffer. I tracked it down to Rustic setting it from rustic-analyzer-command. Once I had replaced the variable in the above configuration, lsp-mode was finally able to find the binary, but there was no difference in the highlighting. This was easier to figure out. *lsp-log* indicated that it found both rls and rust-analyzer and was using rls. I closed Emacs, ran cargo uninstall rls, then deleted the rls.exe binary left in my .cargo/bin (which I must have manually installed long ago). Finally, I restarted Emacs… and was greeted with rich, semantic highlighting of my Rust code! (I’m not sure why it shows {unknown}. When it works, it shows the documentation of the item under the cursor. It seems unreliable, though, and I find it distracting, so I may disable it.)

An Emacs buffer showing a properly highlighted Rust file.