199

Coding C# in Neovim

 3 years ago
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.
neoserver,ios ssh client

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 and grepper 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:

  1. <leader>fu for 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.
  2. <leader>gd for 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).
  3. <leader>rn for 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
  4. <leader>xd will open a Telescope window with all of the errors, warnings, etc. in the current open buffer.
  5. <leader>xD will open a Telescope window with all of the errors, warnings, etc. across the entire current project.
  6. <leader>xn jumps to the next warning or error in the current buffer.
  7. <leader>xp jumps to the previous warning or error in the current buffer.
  8. <leader>xx will open a Telescope window with all of the possible code actions on the error or warning item under the cursor.
  9. <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 and vim-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 usings 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 Comments

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK