rockyourcode: Use Vim for C# Development on Linux
Hello 👋! Thanks for subscribing.
Use Vim for C# Development on Linux
Published on: 2021-01-12
tags: C-Sharp, Vim, DevTools
Vim Language Server Protocol support for C#
If you’re an avid Vim user, you’ll likely try to use the terminal editor for everything text-related.
In this post, I’ll show you how to get convenient support for C# with Vim on Linux.
C# is traditionally one of those programming languages that profit from an IDE, an integrated development environment.
Vim can still be a viable alternative if you need minimal features like type definitions or auto-completion.
Install NET.Core
As an Arch Linux user, my first instinct is to install packages with the Arch package manager.
Somehow, this seems to conflict with the language server we’ll install in a later step.
Thus, I recommend a manual install. The Arch Linux wiki explains how. The instructions work for other distributions, too.
-
Download the dotnet-install.sh script for Linux.
-
Run the script for the stable version:
chmod +x dotnet-install.sh
./dotnet-install.sh --install-dir /usr/share/dotnet -channel LTS -version latest
(You might need sudo
because the normal user does not have permissions for the /usr/share/dotnet
folder.)
Install Language Server
We need OmniSharp Roslyn, a cross-platform language server implementation.
The README of the project is densely packed with information. I originality tried to build the executable from scratch because that’s prominently featured. But it’s not necessary and can lead to frustration.
Go to the releases tab and choose a suitable pre-build release.
For example, download the 1.37.5 release for 64-bit Linux with curl and extract it to $HOME/.bin
folder:
curl -sL https://github.com/OmniSharp/omnisharp-roslyn/releases/download/v1.37.5/omnisharp-linux-x64.tar.gz | tar xvzf - -C ~/home/.bin
Install LSP
Vim needs a plugin for the Language Server Protocol.
I am using prabirshrestha/vim-lsp, an asynchronous implementation that works both in Vim 8 and NeoVim. The plugin uses VimL and thus has no external dependencies.
Install with native package support or a plugin manager of your choice. Example:
cd ~/vim/pack
git submodule init
git submodule add https://github.com/prabirshrestha/vim-lsp.git
git add .gitmodules vim/pack/prabirshrestha/vim-lsp
git commit
Now register the OmniSharp Language Server. I’ve copied my setup from an article by a fellow tech blogger (hauleth.dev). I created a new file in my Vim folder (~/vim/plugin/lsp.vim
) with the following content:
func! s:setup_ls(...) abort
let l:servers = lsp#get_whitelisted_servers()
for l:server in l:servers
let l:cap = lsp#get_server_capabilities(l:server)
if has_key(l:cap, 'completionProvider')
setlocal omnifunc=lsp#complete
endif
if has_key(l:cap, 'hoverProvider')
setlocal keywordprg=:LspHover
endif
if has_key(l:cap, 'definitionProvider')
nmap <silent> <buffer> gd <plug>(lsp-definition)
endif
if has_key(l:cap, 'referencesProvider')
nmap <silent> <buffer> gr <plug>(lsp-references)
endif
endfor
endfunc
augroup LSC
autocmd!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'omnisharp-roslyn',
\ 'cmd': {_->[&shell, &shellcmdflag, 'mono $HOME/.bin/omnisharp/OmniSharp.exe --languageserver']},
\ 'whitelist': ['cs']
\})
autocmd User lsp_server_init call <SID>setup_ls()
autocmd BufEnter * call <SID>setup_ls()
augroup END
Note: You don’t need to create a new file for the setup, of course. Just find a way to add the settings to Vim/NeoVim (for example, via init.vim
configuration).
Note: If you installed OmniSharp into a different directory than $HOME/.bin
, you need to adjust the cmd
section.
&shell
and &shellcmdflag
are specific to vim-lsp (and not really necessary on Linux):
It is recommended to use &shell with &shellcmdflag when running script files that can be executed specially on windows where _.bat and _.cmd files cannot be started without running the shell first. This is common for executable installed by npm for nodejs.
mono
is the utility that allows you to run .exe
files under Linux. It should be on your machine thanks to the .NET Core installation.
Now, as soon as you open a file with filetype cs
(for C#), the language server will automatically kick in.
You could type K
(in normal mode) when you hover over a keyword, and you’ll get some informations about the word under the cursor.
Syntax Highlighting
Syntax highlighting works out of the box with the Vim runtime.
Bonus: Formatting
I could not find a sanctioned solution for formatting C#. For now, I’m using Uncrustify, a code beautifier for C-style languages.
This tool is not Vim-specific. I run it from the terminal or via external shell command in Vim.
Install a pre-compiled binary from GitHub or use your operating system’s package manager.
You can customize Uncrustify to your liking and you need a default configuration file.
Here are my settings (~/.uncrustify.cfg
):
#
# Formatter for c#, java, etc.
#
newlines = LF # AUTO (default), CRLF, CR, or LF
indent_with_tabs = 0 # 1=indent to level only, 2=indent with tabs input_tab_size = 8 # original tab size output_tab_size = 3 # new tab size indent_columns = output_tab_size
indent_label = 0 # pos: absolute col, neg: relative column
indent_align_string = False # align broken strings indent_brace = 0 indent_class = true
nl_start_of_file = remove
nl_start_of_file_min = 0
nl_end_of_file = force nl_end_of_file_min = 1 nl_max = 4 nl_before_block_comment = 2 nl_after_func_body = 2 nl_after_func_proto_group = 2
nl_assign_brace = add # "= {" vs "= \n {" nl_enum_brace = add # "enum {" vs "enum \n {" nl_union_brace = add # "union {" vs "union \n {" nl_struct_brace = add # "struct {" vs "struct \n {" nl_do_brace = add # "do {" vs "do \n {" nl_if_brace = add # "if () {" vs "if () \n {" nl_for_brace = add # "for () {" vs "for () \n {" nl_else_brace = add # "else {" vs "else \n {" nl_while_brace = add # "while () {" vs "while () \n {" nl_switch_brace = add # "switch () {" vs "switch () \n {" nl_func_var_def_blk = 1 nl_before_case = 1 nl_fcall_brace = add # "foo() {" vs "foo()\n{" nl_fdef_brace = add # "int foo() {" vs "int foo()\n{" nl_after_return = TRUE nl_brace_while = remove nl_brace_else = add nl_squeeze_ifdef = TRUE
pos_bool = trail # BOOL ops on trailing end
eat_blanks_before_close_brace = TRUE eat_blanks_after_open_brace = TRUE
mod_paren_on_return = add # "return 1;" vs "return (1);" mod_full_brace_if = add # "if (a) a--;" vs "if (a) { a--; }" mod_full_brace_for = add # "for () a--;" vs "for () { a--; }" mod_full_brace_do = add # "do a--; while ();" vs "do { a--; } while ();" mod_full_brace_while = add # "while (a) a--;" vs "while (a) { a--; }"
sp_before_byref = remove sp_before_semi = remove sp_paren_paren = remove # space between (( and )) sp_return_paren = remove # "return (1);" vs "return(1);" sp_sizeof_paren = remove # "sizeof (int)" vs "sizeof(int)" sp_before_sparen = force # "if (" vs "if(" sp_after_sparen = force # "if () {" vs "if (){" sp_after_cast = remove # "(int) a" vs "(int)a" sp_inside_braces = force # "{ 1 }" vs "{1}" sp_inside_braces_struct = force # "{ 1 }" vs "{1}" sp_inside_braces_enum = force # "{ 1 }" vs "{1}" sp_inside_paren = remove sp_inside_fparen = remove sp_inside_sparen = remove sp_inside_square = remove
sp_type_func = ignore
sp_assign = force sp_arith = force sp_bool = force sp_compare = force sp_assign = force sp_after_comma = force sp_func_def_paren = remove # "int foo (){" vs "int foo(){" sp_func_call_paren = remove # "foo (" vs "foo(" sp_func_proto_paren = remove # "int foo ();" vs "int foo();" sp_func_class_paren = remove sp_before_angle = remove sp_after_angle = remove sp_angle_paren = remove sp_angle_paren_empty = remove sp_angle_word = ignore sp_inside_angle = remove sp_inside_angle_empty = remove sp_sparen_brace = add sp_fparen_brace = add sp_after_ptr_star = remove sp_before_ptr_star = force sp_between_ptr_star = remove
align_with_tabs = FALSE # use tabs to align align_on_tabstop = FALSE # align on tabstops align_enum_equ_span = 4 align_nl_cont = TRUE align_var_def_span = 1 align_var_def_thresh = 12 align_var_def_inline = TRUE align_var_def_colon = TRUE align_assign_span = 1 align_assign_thresh = 12 align_struct_init_span = 3 align_var_struct_span = 99 align_right_cmt_span = 3 align_pp_define_span = 3 align_pp_define_gap = 4 align_number_right = TRUE align_typedef_span = 5 align_typedef_gap = 3 align_var_def_star_style = 0
cmt_star_cont = TRUE
Above you see the options from a GitHub configuration file with changes made from this issue.
Thoughts
Using Vim for C# development is a tad wonky. I’ve had better success with languages like Go or OCaml. But in a pinch, it works — even on Linux.
Links
- Install multiple .NET Core versions manually
- vim-lsp
- OmniSharp Roslyn
- Vim: So long Pathogen, hello native package loading
- Dumb Elixir VIsual (and iMproved) editor
- Uncrustify
Thank you for reading my blog posts.