Coding C# in Neovim
In this post I’ll detail how I set up a new C# coding environment using Neovim‘s native LSP support. This guide assumes you’re starting completely fresh, as I did just prior to writing this. My goal was to see if I like the Neovim setup as much as or better than the Vim setup I’ve been using for years. My conclusion on that is at the end of the article.
Should be easy enough. Install it using your distribution’s package manager or whatever other method you’re comfortable with. On my Arch machine I did it like this:
pacman -S neovim
I don’t believe anything I’m using requires the bleeding-edge
nightly version. I’m not using it, anyway, and everything I wanted seems to be working.
You’ll want the latest version of omnisharp-roslyn, which is what Neovim will launch in the background and use as the LSP server whenever you open a
.cs file. You might be able to find it with your package manager (it’s in the AUR, for example), or you can just download the latest
omnisharp-linux-x64.tar.gz release and decompress that anywhere you want.
Neovim has a variety of package managers available. My goal is to use plugins written and configured in Lua whenever possible, because I really don’t like (or understand) vimscript and Lua looks and feels a lot nicer to me. I chose packer.nvim and simply followed the instructions on their github:
$> git clone --depth 1 https://github.com/wbthomason/packer.nvim\ ~/.local/share/nvim/site/pack/packer/start/packer.nvim
I created the file
~/.config/nvim/lua/plugins.lua with the following content:
- telescope.nvim - Not strictly required, but provides a nice floating-window interface to several of the LSP functions, and is also a nice pure-Lua substitute for both the
grepperplugins I use in Vim.
- nvim-lspconfig - This is what automatically launches LSP servers (such as Omnisharp) in the background so that Neovim can talk to them.
- nvim-cmp - This seems to be the most widely recommended completion engine for Neovim.
- cmp-nvim-lsp - Enables LSP completions.
Then at the top of your
~/.config/nvim/init.vim (which you should create if it doesn’t exist already—perhaps by making a copy of your
~/.vimrc file, although I created mine from scratch because my vim config is quite bloated and I wanted to selectively enable only the things I actually still need or want):
After you save that file, then close and re-launch Neovim, you can run this ex command to install all the plugins we just added:
These configuration blocks go into the same
plugins.lua file you created in the previous step, below the
use statements and above
This is enabling the autocomplete plugin, adding a couple keybindings so you can tab and shift-tab between items in the menu, and telling it to use LSP as a completions source. I haven’t played with the keybindings too much, so that is still a prime target for tweaking.
If you also installed other completion sources you would also add those here to the
This is instructing
nvim-lspconfig that we want to enable the
omnisharp LSP provider. It’s also setting up completions with the
on_attach lines, but I’m not entirely sure what those are actually doing. All I know is that completions don’t work without them.
You’ll want to replace the path on line
7 with the correct path to
omnisharp-roslyn that you obtained earlier.
These are the keybindings I use, as an example, but obviously you should tweak these to your own personal preference. In your
~/.config/nvim/init.vim file. In general, the functions you’ll probably be interested in mapping are the
:Telescope lsp_* ones, the
:lua vim.lsp.buf.* ones, and the
:lua vim.lsp.diagnostic.* ones:
Explanation by line number:
find-usings, will open a Telescope window with all the places in your code that the current symbol under your cursor is used in the rest of the project.
goto-definition, will jump to the definition of the current symbol under your cursor (or provide a Telescope list if there are more than one).
rename, will prompt you for a new name to give the current symbol under your cursor, and refactor all references to it in the current project
<leader>xdwill open a Telescope window with all of the errors, warnings, etc. in the current open buffer.
<leader>xDwill open a Telescope window with all of the errors, warnings, etc. across the entire current project.
<leader>xnjumps to the next warning or error in the current buffer.
<leader>xpjumps to the previous warning or error in the current buffer.
<leader>xxwill open a Telescope window with all of the possible code actions on the error or warning item under the cursor.
<leader>xXwill open a Telescope window with all of the possible code actions on all errors or warnings in the current document.
I edited the
<leader>xX mapping above to a more useful action (document-wide code actions instead of project-wide code actions).
If everything is set up correctly, when you open a
.cs file with Neovim, it should launch
omnisharp-roslyn in the background and start giving you feedback (errors, warnings, autocomplete-as-you-type, and so on). When you’re in the file you can run
:LspInfo for a dialog with details about the current LSP provider(s). It should say
1 client(s) attached to this buffer and
Client: omnisharp in there.
Overall I’m pretty happy with this setup so far. The completions feel snappier than in Vim, and I really like Telescope in general. Some other plugins that I’ve also got going right now to complete the setup:
- lualine.nvim for a slightly fancier status line (replacing
vim-airlinefrom my Vim setup)
- gitsigns.nvim for git indicators (replacing
gitgutterfrom my Vim setup)
- I don’t need a replacement for
vim-grepperbecause Telescope already covers those
- vim-filebeagle is the one hold-out that I copied from my Vim setup—it’s just too useful, simple, and ingrained in my muscle memory to live without or care about finding an alternative
I’ve also installed a few additional supported LSP providers and configured them similar to how Omnisharp was configured above. For languages that don’t have LSP providers (such as
shellcheck for POSIX shell scripts) I’ve installed efm-langserver, which I’m hoping should let me avoid something like
ALE or language-specific plugins, and just stick with Neovim’s built-in LSP support across the board. That way the general behavior and keybindings I configured above will be consistent regardless of what language I’m working in.
So far the only thing I’m missing during C# development is the
OmniSharpFixUsings command that
omnisharp-vim provided. As far as I can tell, that functionality is not (yet) exposed through the LSP interface of
omnisharp-roslyn. It’s not too terrible a loss, however, because my
<leader>xX mapping will pull up all possible code actions in the current document, so I can repeat that and add all the missing
using statements one at a time. It’s not as quick as running a single command and having all the missing
usings added for you, but it’s an acceptable alternative, especially since the
OmniSharpFixUsings feature had some annoying idiosyncrasies around when there were multiple different choices for a
I hope this helps some fellow Vim-loving Linux C# developers out there. Happy (Neo)vimming!
I’ve figured out how to get code analyzers and
.editorconfig support working now. Create a file named
omnisharp.json at your project root if you don’t have one already, and make sure it has the following options enabled:
Now create your
.editorconfig at the project root. You can pull in the example config from the omnisharp-roslyn repo. After restarting everything, you should start seeing code analysis and formatting suggestions along side your syntax errors and warnings.
Then if you also install the editorconfig.nvim plugin, it will make it so you don’t need filetype plugins or other custom Neovim configuration to change your indent type or size for
.cs files (it will read those from your
.editorconfig and do that for you).
Short Permalink for Attribution: rdsm.ca/4fpea