Coding C# in Neovim
source link: https://rudism.com/coding-csharp-in-neovim/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Coding C# in Neovim
2021-09-01 (posted in blog)Inspired to try Neovim again by an article I read recently, this is an update to replace my older Coding C# in Vim article, which is woefully outdated.
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.
Step 1: Obtain Neovim
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.
Step 2: Obtain Omnisharp
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.
Step 3: Install Neovim Plugin Manager
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
Step 4: Install Plugins
I created the file ~/.config/nvim/lua/plugins.lua
with the following content:
return require('packer').startup(function()
use 'wbthomason/packer.nvim' -- so packer can update itself
use { -- nice interface for LSP functions (among other things)
'nvim-telescope/telescope.nvim',
requires = { {'nvim-lua/plenary.nvim'} }
}
use 'neovim/nvim-lspconfig' -- native LSP support
use 'hrsh7th/nvim-cmp' -- autocompletion framework
use 'hrsh7th/cmp-nvim-lsp' -- LSP autocompletion provider
end)
- 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
fzf
andgrepper
plugins 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.
You may also want to check out hrsh7th‘s other repositories for additional completion sources (I also use buffer and path).
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):
lua require('plugins')
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:
:PackerUpdate
Step 5: Configure Plugins
These configuration blocks go into the same plugins.lua
file you created in the previous step, below the use
statements and above end)
.
Autocompletions
-- autocomplete config
local cmp = require 'cmp'
cmp.setup {
mapping = {
['<Tab>'] = cmp.mapping.select_next_item(),
['<S-Tab>'] = cmp.mapping.select_prev_item(),
['<CR>'] = cmp.mapping.confirm({
behavior = cmp.ConfirmBehavior.Replace,
select = true,
})
},
sources = {
{ name = 'nvim_lsp' },
}
}
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 sources
section.
Omnisharp LSP
-- omnisharp lsp config
require'lspconfig'.omnisharp.setup {
capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities()),
on_attach = function(_, bufnr)
vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
end,
cmd = { "/path/to/omnisharp-roslyn/bin/omnisharp/run", "--languageserver" , "--hostPID", tostring(pid) },
}
This is instructing nvim-lspconfig
that we want to enable the omnisharp
LSP provider. It’s also setting up completions with the capabilities
and 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.
Step 6: Configure Keybindings
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:
nnoremap <silent> <leader>fu <cmd>Telescope lsp_references<cr>
nnoremap <silent> <leader>gd <cmd>Telescope lsp_definitions<cr>
nnoremap <silent> <leader>rn <cmd>lua vim.lsp.buf.rename()<cr>
nnoremap <silent> <leader>xd <cmd>Telescope lsp_document_diagnostics<cr>
nnoremap <silent> <leader>xD <cmd>Telescope lsp_workspace_diagnostics<cr>
nnoremap <silent> <leader>xn <cmd>lua vim.lsp.diagnostic.goto_next()<cr>
nnoremap <silent> <leader>xp <cmd>lua vim.lsp.diagnostic.goto_prev()<cr>
nnoremap <silent> <leader>xx <cmd>Telescope lsp_code_actions<cr>
nnoremap <silent> <leader>xX <cmd>Telescope lsp_workspace_code_actions<cr>
Explanation by line number:
<leader>fu
forfind-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.<leader>gd
forgoto-definition
, will jump to the definition of the current symbol under your cursor (or provide a Telescope list if there are more than one).<leader>rn
forrename
, 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>xd
will open a Telescope window with all of the errors, warnings, etc. in the current open buffer.<leader>xD
will open a Telescope window with all of the errors, warnings, etc. across the entire current project.<leader>xn
jumps to the next warning or error in the current buffer.<leader>xp
jumps to the previous warning or error in the current buffer.<leader>xx
will open a Telescope window with all of the possible code actions on the error or warning item under the cursor.<leader>xX
will open a Telescope window with all of the possible code actions on all errors or warnings across the entire project (this has dubious usefulness in my experience, I doubt I will use it much)
Conclusion
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-airline
from my Vim setup) - gitsigns.nvim for git indicators (replacing
gitgutter
from my Vim setup) - I don’t need a replacement for
fzf
andvim-grepper
because 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 I can still navigate to a specific error caused by a missing using
with <leader>xn
and <leader>xp
, then use <leader>xx
to pull up the code actions and select the action to add the missing using
(which seems to always be the first one). It’s not as nice as just running a single command and having all the missing using
s added for you at once, but it’s an acceptable alternative for now, and the OmniSharpFixUsings
feature had some annoying idiosyncrasies around when there were multiple choices for a using
anyway.
I hope this helps some fellow Vim-loving Linux C# developers out there. Happy (Neo)vimming!
Short Permalink for Attribution: rdsm.ca/4fpea
0 CommentsRecommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK