rockyourcode

Subscribe
Archives
January 13, 2021

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.

  1. Download the dotnet-install.sh script for Linux.

  2. 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.

Don't hesitate to reach out via email or Twitter!

Don't miss what's next. Subscribe to rockyourcode:
GitHub X
Powered by Buttondown, the easiest way to start and grow your newsletter.