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 {
  "OmniSharp/omnisharp-vim",
  init = function()
    require("lazyvim.util").on_attach(function(client, _)
      if client.name == "omnisharp" then
        client.server_capabilities.semanticTokensProvider = {
          full = vim.empty_dict(),
          legend = {
            tokenModifiers = { "static_symbol" },
            tokenTypes = {
              "comment",
              "excluded_code",
              "identifier",
              "keyword",
              "keyword_control",
              "number",
              "operator",
              "operator_overloaded",
              "preprocessor_keyword",
              "string",
              "whitespace",
              "text",
              "static_symbol",
              "preprocessor_text",
              "punctuation",
              "string_verbatim",
              "string_escape_character",
              "class_name",
              "delegate_name",
              "enum_name",
              "interface_name",
              "module_name",
              "struct_name",
              "type_parameter_name",
              "field_name",
              "enum_member_name",
              "constant_name",
              "local_name",
              "parameter_name",
              "method_name",
              "extension_method_name",
              "property_name",
              "event_name",
              "namespace_name",
              "label_name",
              "xml_doc_comment_attribute_name",
              "xml_doc_comment_attribute_quotes",
              "xml_doc_comment_attribute_value",
              "xml_doc_comment_cdata_section",
              "xml_doc_comment_comment",
              "xml_doc_comment_delimiter",
              "xml_doc_comment_entity_reference",
              "xml_doc_comment_name",
              "xml_doc_comment_processing_instruction",
              "xml_doc_comment_text",
              "xml_literal_attribute_name",
              "xml_literal_attribute_quotes",
              "xml_literal_attribute_value",
              "xml_literal_cdata_section",
              "xml_literal_comment",
              "xml_literal_delimiter",
              "xml_literal_embedded_expression",
              "xml_literal_entity_reference",
              "xml_literal_name",
              "xml_literal_processing_instruction",
              "xml_literal_text",
              "regex_comment",
              "regex_character_class",
              "regex_anchor",
              "regex_quantifier",
              "regex_grouping",
              "regex_alternation",
              "regex_text",
              "regex_self_escaped_character",
              "regex_other_escape",
            },
          },
          range = true,
        }
      end
    end)
  end,
}

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.