Making C# and OmniSharp play well with Neovim

I’ve recently moved away from my custom Neovim configuration to embrace LazyVim. LazyVim is a Neovim setup with sane default settings for options, autocmds, and keymaps. It boldly aims to transform Neovim into a full-fledged IDE that is easy to extend and customize. It comes with a wealth of plugins pre-configured and ready to use, and it is also blazing fast. Elijah Manor has a fantastic introductory video on YouTube; I suggest you take the time to look at it.

So far I’m delighted with the result. In the process, I learned about several useful plugins I now use regularly.

Neovim trouble with C# and OmniSharp

When I upgraded my old-ish Neovim (I am using nightly builds now), I started getting a weird error on every .cs file I loaded:

Error executing vim.schedule lua callbaack: /usr/share/[...]/semantic_tokens.lua:342:E5248: Invalid character in group name.

A little investigation revealed that semantic tokens provided by OmniSharp don’t conform to the LSP specification, which triggers the error. I cloned the omnisharp-roslyn repo and dug into the code hoping I could offer a quick fix. As it turns out, however, the issue is actually with Roslyn itself, not OmniSharp. There are tickets on both the Neovim and the OmniSharp repositories, but I fear they’ll stagnate there as non-relevant (note to self: maybe report the problem to the Roslyn folks? Alternatively, propose a patched semantic provider to the omnisharp-roslyn maintainers.)

Anyway, a quick, hacky, and not future-proof fix is to customize Neovim (LazyVim) configuration like this:

-- ~/.config/nvim/lua/plugins/omnisharp.lua (create if needed)
return {
  init = function()
    require("lazyvim.util").on_attach(function(client, _)
      if == "omnisharp" then
        client.server_capabilities.semanticTokensProvider = {
          full = vim.empty_dict(),
          legend = {
            tokenModifiers = { "static_symbol" },
            tokenTypes = {
          range = true,

It overrides the on_attach event to pass an LSP-digestible list of semantic tokens. And voilĂ , C# files are now loaded seamlessly.

I’m not done yet. I’m having another weird issue with .editorconfig files. I’m still triaging it, and will report back when (if) I sort it out.

Subscribe to the newsletter, the RSS feed, or follow me on Mastodon