[Esc] $IDE

Introduction

There are only two hard things in Computer Science: cache invalidation and naming things.

– Phil Karlton

(via @martinfowler)

It probably goes without much saying, but IDEs do bring a lot of value to the programmers workplace and in practice get the job done for a lot of people. Especially when programming in statically typed languages they really shine, easing intrusive changes to even large code bases with the compiler and types giving us some strong guarantees as to the correctness of our code. This effectively enables us to keep up a good development pace while make design-oriented, not time- and thus budget-oriented choices during development cycles.

Renaming types, variables and functions/methods with the right tools is a breeze and does not only not get in the way, but is in and by itself an important part of the overall design process. I find myself renaming stuff much more liberally nowadays, especially when it becomes clear the meaning or place of a type, function or variable in the architecture have changed so significantly that keeping its old name around becomes too big a cognitive burden for oneself and collaborators to bear. With tools that are able to work directly on an in-memory ASTs of a source file, semantically meaninful manipulation of our code is fast and accurate.

However, there is a lot of value in extensibility and freedom to mold according to personal preferences. While most modern IDEs I have laid fingers on, do have some sort of mechanism for adding functionality via plugin interfaces, they are not truly modular pieces of software. You are, in the end, forced to either accept the design choices made by its authors, or find an alternative.

This blog post documents my findings getting into C#/F# development with a variety of tools, and some Tips for setting up GNU/Emacs as a C#/F# IDE. Please bear in mind that I am writing this for people with some level of experience in Emacs, yet who are new to .NET development (like myself).

Visual Studio

When recently embarking on the development of a larger, mixed C#/F# project I decided to get started using the “standard” tools, Visual Studio Community Edition (with the excellent vim editing extension), as I my experience with the Mono/.NET tool chain was very limited. Visual Studio is certainly a great environment providing for everything one could wish for, except, of course, when it doesn’t.

For instance, I often found myself wishing for an Emacs-y way to get documentation for editor functions, key bindings and settings, referring to web search when I couldn’t find any information. Not that this doesn’t happen when I tweak my init.el, but nowadays, I often try to see if I can find out about a problem the built-in way first, then refer to the web when information is sparse.

When I suddenly realised though, that I might have to get a license for VS soon┬╣ and found out how much that investment was going to be (combined with the fact that in all honesty, I fundamentally do not want to spend any time in Windows to begin with), I decided to explore the alternatives.

Mono Develop

My next stop was Mono Develop. It looks a bit like i*unes for some reason :), works on all platforms I care for, supports both, C# and F# projects, and its free software. Free as in ride and speech, another important incentive to move away from Visual Studio!

I installed the vim editing extension and started hacking away (to my delight I realised that I could also build my project with a copy running on GNU/Linux and share the build output via a shared drive with Windows).

However, it did not take long to discover a major problem with Mono Develop - its editing facilities. Specifically, its vi extension only supports a tiny subset of what vim users tend to expect, which proved to be a great source of frustration.

For instance, working with rectangles (Ctrl-v), repeat last action (.) and many more commands do not work as expected (or at all), so it quickly became clear I had to move on. So, eff it, down the rabbit hole…

Emacs

Because..

1) I generally try and do anything in Emacs, and 2) my stint in the Windows world should stay exactly that - just a stint :)

… the next logical step was to try and set it up for C#/F# development.

FSharp

I was really happy to find that F# support for Emacs is all in all excellent, with completion and syntax checking, working well out of the box. I use Cask for Emacs dependency management, so the only thing I had to do was adding

(source gnu)
(source melpa)
(source org)

;;; ... your stuff

(depends-on "fsharp-mode")

to ~/.emacs.d/Cask, run cask and restart Emacs.

Fsharp mode integrates with company mode for code completion. Here is a screenshot of it completing a little logging module, showing the type of my Debug function. This also demos how the mode highlights errors, in this case a missing let binding.

FSharp & company-mode in action

Super nice, and no configuration was necessary to get this working!

Support for operations like refactoring or renaming seems to be missing in this mode at the time of this writing. Maybe a good idea for a pull request?

CSharp

Setting up C# was only slightly more involved to set up. A while ago, I found out about OmniSharp-server, a command line daemon serving a REST-style HTTP API used to fetch completion candidates & information to enable renaming types/variables, among others.

The basic work flow is to start the command line executable with the -s flag pointing to the directory of the solution file, e.g.:

$ ./OmniSharp/bin/Debug/OmniSharp.exe -s ~/src/myprojects/project-name

The daemon will read all source files and register inotify event handlers on them to track the changes you make while editing. But first, to get started you need to install Mono, download OmniSharp-server source code and build it (code pasted from its github page).

$ sudo pacman -S mono # if you're on Arch..
$ git clone https://github.com/nosami/OmniSharpServer.git
$ cd OmniSharpServer
$ git submodule update --init --recursive
$ xbuild

In order to use it, install csharp-mode via package.el or whatever method you prefer. Additionally you’ll need the OmniSharp-emacs package. So, in my Cask file, it all boils down to this:

(source gnu)
(source melpa)
(source org)

;;; ... your stuff

(depends-on "fsharp-mode")
(depends-on "csharp-mode")
(depends-on "omnisharp")

Run cask and restart Emacs for the changes to take effect. (Note, there might be a more efficient of handling packages by sharing the same ELPA directory between package-install and cask, but I have not had the need or muse to get that straightened out).

To activate and configure modes, I added the following elisp to my

configuration:

(defun csharp-hs-forward-sexp (&optional arg)
  (let ((nestlevel 0)
        (mark1 (point))
        (done nil))

    (if (and arg (< arg 0))
        (message "negative arg (%d) is not supported..." arg)

      ;; else, we have a positive argument, hence move forward.
      ;; simple case is just move forward one brace
      (if (looking-at "{")
          (forward-sexp arg)
        (and
         (while (not done)
           (re-search-forward "^[ \\t]*#[ \\t]*\\(region\\|endregion\\)\\b" (point-max) \'move)
           (cond ((eobp))
                 ((and
                   (match-beginning 1)
                   ;; if the match is longer than 6 chars, we know it is "endregion"
                   (if (> (- (match-end 1) (match-beginning 1)) 6)
                       (setq nestlevel (1- nestlevel))
                     (setq nestlevel (1+ nestlevel))
                     )
                   )))
           (setq done (not (and (> nestlevel 0) (not (eobp))))))
         (if (= nest 0)
             (goto-char (match-end 2)))))))))
             
(defun csharp-mode-setup ()
  (interactive)
  (setq-local c-basic-offset 4)
  (add-to-list 'company-backends 'company-omnisharp)
  (c-set-style "c#")
  (c-set-offset 'arglist-intro 4)
  (c-set-offset 'substatement-open 0))

(custom-set-variables
 ;;;; HIDE/SHOW
 '(hs-special-modes-alist
   '((c-mode "{" "}" "/[*/]" nil nil)
     (c++-mode "{" "}" "/[*/]" nil nil)
     (bibtex-mode ("@\\S(*\\(\\s(\\)" 1))
     (java-mode "{" "}" "/[*/]" nil nil)
     (js-mode "{" "}" "/[*/]" nil)
     (csharp-mode
      "\\(^[ \\t]*#[ \\t]*region\\b\\)\\|{"  ; regexp for start block
      "\\(^[ \\t]*#[ \\t]*endregion\\b\\)\\|}"   ; regexp for end block
      "/[*/]"                                ; regexp for comment start
      csharp-hs-forward-sexp                 ; hs-forward-sexp-func
      hs-c-like-adjust-block-beginning       ;c-like adjust (1 char)
      ))))))

(add-hook 'csharp-mode-hook 'omnisharp-mode)
(add-hook 'csharp-mode-hook 'csharp-mode-setup)

The function to add support to Hide/Show mode for #region is from this blog post. Apart from that, the setup callback sets some buffer-local variables, such as indentation offsets for different contexts, and adds company-omnisharp backend on the global list of company completion backends.

Completion Demo

Here are a few screens demoing the completion backend:

Complete local symbols

Complete usings

Complete contructors

Complete methods

One tricky thing I realized was that for completion to work on all imported libraries, they may need to be added to the list of references in the project. In order to do that, I tend to fire up Mono Develop and do it there.

Renaming Demo

A gif says more than 0x3E8 words :)

Renaming method

There is a lot more to explore with respect to OmniSharp-server that I could not cover, partly because I have yet to refine my setup and explore all that stuff.

Package Management

To manage package dependencies you’ll want to install nuget.exe. It is in Arch Linux’ user repository (AUR), but you might have to install it manually depending on your environment.

Don’t get weirded out by this:

bah, escape sequences..

It happens due to color escape sequences that the terminal cannot process. You can either set your $TERM variable to something like xterm (but not xterm-256color!) or choose to ignore it (recommended ;)).

If you just cloned a solution from VCS, then likely you don’t have all the dependencies installed. To do this from the command line, navigate to your project (the directory with a packages.config file in it) and execute

$ nuget install 

You might have to add the -Prerelease switch to include those as well.

Compiling

This little tutorial would not be complete without some information on how to build your projects using Emacs.

As of a couple of days ago, there are now 2 ways that could be done: using Mono’s xbuild tool, or msbuild which was recently open-sourced by Microsoft. I’m covering xbuild, since its free :)

To build the Debug configuration for x64 processors of your Solution, execute from the command line:

$ xbuild /p:Configuration='Debug' /p:Platform='x64'

To clean your solution, execute the following command:

$ xbuild /target:Clean

In order not to have to leave Emacs, use M-x compile and M-x recompile and/or bind those to keys (I have set them to , cc and , cr, respectively).

Conclusion

While some trade-offs might apply, this setup is already enough for me to be really productive. There are a few reasons to keep Mono Develop around though. As far as I know, there are no tools for managing the configuration XMLs of projects/solutions or for generating new projects/solutions from scratch.

Also, refactoring/renaming does not seem to be supported in FSharp mode so far.

But yeah… so glad I can spend most of my dev time in Emacs again though :)

Updates:

Flycheck support:

It turns out fixing flycheck is really simple: just add the following buffer-local variable to you csharp setup callback:

(setq-local flycheck-checker 'csharp-omnisharp-curl)

This enables syntax checks, code issues and more. Yay!

Package management:

\@ryansroberts suggests using Paket for dependency management. It does look a lot neater, and comes with batteries included (source code, github support etc).


[1] ps: Is this actually correct? It is free? Then why does it tell me it isn’t and I should buy it? Nevermind, don’t really need it anymore anyways :)